@propbinder/mobile-design 0.2.91 → 0.2.97

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, effect, Injectable, inject, Input, Component, input, Directive, EventEmitter, HostListener, Output, PLATFORM_ID, ElementRef, output, CUSTOM_ELEMENTS_SCHEMA, TemplateRef, ContentChild, forwardRef, ViewChild, afterNextRender, ViewEncapsulation, model, createComponent, ChangeDetectionStrategy, NgZone, HostBinding } from '@angular/core';
2
+ import { signal, computed, effect, Injectable, inject, Input, Component, input, output, Directive, EventEmitter, HostListener, Output, PLATFORM_ID, ElementRef, CUSTOM_ELEMENTS_SCHEMA, TemplateRef, ContentChild, forwardRef, ViewChild, afterNextRender, ViewEncapsulation, model, createComponent, ChangeDetectionStrategy, NgZone, Pipe, HostBinding, InjectionToken } from '@angular/core';
3
3
  import * as i1$1 from '@angular/common';
4
4
  import { CommonModule, isPlatformBrowser } from '@angular/common';
5
5
  import * as i1$3 from '@angular/router';
@@ -689,39 +689,91 @@ class DsMobileGlassSpinnerComponent {
689
689
  */
690
690
  borderRadius = input(0, ...(ngDevMode ? [{ debugName: "borderRadius" }] : []));
691
691
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileGlassSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
692
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DsMobileGlassSpinnerComponent, isStandalone: true, selector: "ds-mobile-glass-spinner", inputs: { spinnerSize: { classPropertyName: "spinnerSize", publicName: "spinnerSize", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
693
- <div
694
- class="glass-spinner-overlay"
695
- [style.border-radius.px]="borderRadius()"
696
- role="status"
697
- aria-live="polite"
698
- aria-label="Loading">
699
- <div
700
- class="spinner"
701
- [style.width.px]="spinnerSize()"
702
- [style.height.px]="spinnerSize()">
703
- </div>
704
- </div>
692
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DsMobileGlassSpinnerComponent, isStandalone: true, selector: "ds-mobile-glass-spinner", inputs: { spinnerSize: { classPropertyName: "spinnerSize", publicName: "spinnerSize", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
693
+ <div
694
+ class="glass-spinner-overlay"
695
+ [style.border-radius.px]="borderRadius()"
696
+ role="status"
697
+ aria-live="polite"
698
+ aria-label="Loading">
699
+ <div
700
+ class="spinner"
701
+ [style.width.px]="spinnerSize()"
702
+ [style.height.px]="spinnerSize()">
703
+ </div>
704
+ </div>
705
705
  `, isInline: true, styles: [":host{display:contents}.glass-spinner-overlay{position:absolute;inset:0;background:#fff6;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:9999;border-radius:inherit}.spinner{border:2px solid var(--color-border-neutral-secondary, #E5E5E5);border-top-color:var(--color-accent, #6B5FF5);border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-color-scheme: dark){.glass-spinner-overlay{background:#0000004d}.spinner{border-color:#fff3;border-top-color:var(--color-accent, #6B5FF5)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
706
706
  }
707
707
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileGlassSpinnerComponent, decorators: [{
708
708
  type: Component,
709
- args: [{ selector: 'ds-mobile-glass-spinner', standalone: true, imports: [CommonModule], template: `
710
- <div
711
- class="glass-spinner-overlay"
712
- [style.border-radius.px]="borderRadius()"
713
- role="status"
714
- aria-live="polite"
715
- aria-label="Loading">
716
- <div
717
- class="spinner"
718
- [style.width.px]="spinnerSize()"
719
- [style.height.px]="spinnerSize()">
720
- </div>
721
- </div>
709
+ args: [{ selector: 'ds-mobile-glass-spinner', standalone: true, imports: [CommonModule], template: `
710
+ <div
711
+ class="glass-spinner-overlay"
712
+ [style.border-radius.px]="borderRadius()"
713
+ role="status"
714
+ aria-live="polite"
715
+ aria-label="Loading">
716
+ <div
717
+ class="spinner"
718
+ [style.width.px]="spinnerSize()"
719
+ [style.height.px]="spinnerSize()">
720
+ </div>
721
+ </div>
722
722
  `, styles: [":host{display:contents}.glass-spinner-overlay{position:absolute;inset:0;background:#fff6;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:9999;border-radius:inherit}.spinner{border:2px solid var(--color-border-neutral-secondary, #E5E5E5);border-top-color:var(--color-accent, #6B5FF5);border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-color-scheme: dark){.glass-spinner-overlay{background:#0000004d}.spinner{border-color:#fff3;border-top-color:var(--color-accent, #6B5FF5)}}\n"] }]
723
723
  }], propDecorators: { spinnerSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "spinnerSize", required: false }] }], borderRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderRadius", required: false }] }] } });
724
724
 
725
+ class DsMobileCountBadgeComponent {
726
+ count = input.required(...(ngDevMode ? [{ debugName: "count" }] : []));
727
+ variant = input('accent', ...(ngDevMode ? [{ debugName: "variant" }] : []));
728
+ display = computed(() => {
729
+ const n = this.count();
730
+ return n > 99 ? '99+' : `${n}`;
731
+ }, ...(ngDevMode ? [{ debugName: "display" }] : []));
732
+ label = computed(() => `${this.count()} notifikationer`, ...(ngDevMode ? [{ debugName: "label" }] : []));
733
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileCountBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
734
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DsMobileCountBadgeComponent, isStandalone: true, selector: "ds-mobile-count-badge", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: true, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "'count-badge count-badge--' + variant()", "attr.aria-label": "label()" } }, ngImport: i0, template: `{{ display() }}`, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:16px;padding:0 6px;border-radius:10px;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:var(--font-size-xs);line-height:1;color:var(--color-on-accent, #ffffff);white-space:nowrap;box-sizing:border-box}:host(.count-badge--accent){background:var(--color-accent, #6B5FF5)}:host(.count-badge--notification){width:16px;height:16px;min-width:16px;padding:0;border-radius:4px;background:var(--content-feedback-bold-red, #b91c1c)}:host(.count-badge--muted){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.2);color:var(--header-content-color, white)}\n"] });
735
+ }
736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileCountBadgeComponent, decorators: [{
737
+ type: Component,
738
+ args: [{ selector: 'ds-mobile-count-badge', standalone: true, host: {
739
+ '[class]': "'count-badge count-badge--' + variant()",
740
+ '[attr.aria-label]': "label()",
741
+ }, template: `{{ display() }}`, styles: [":host{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:16px;padding:0 6px;border-radius:10px;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:var(--font-size-xs);line-height:1;color:var(--color-on-accent, #ffffff);white-space:nowrap;box-sizing:border-box}:host(.count-badge--accent){background:var(--color-accent, #6B5FF5)}:host(.count-badge--notification){width:16px;height:16px;min-width:16px;padding:0;border-radius:4px;background:var(--content-feedback-bold-red, #b91c1c)}:host(.count-badge--muted){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.2);color:var(--header-content-color, white)}\n"] }]
742
+ }], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
743
+
744
+ class DsMobileNotificationButtonComponent {
745
+ count = input(0, ...(ngDevMode ? [{ debugName: "count" }] : []));
746
+ clicked = output();
747
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
748
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileNotificationButtonComponent, isStandalone: true, selector: "ds-mobile-notification-button", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, ngImport: i0, template: `
749
+ <ds-icon-button
750
+ icon="remixNotificationLine"
751
+ variant="secondary"
752
+ size="md"
753
+ (clicked)="clicked.emit()"
754
+ [ariaLabel]="'Notifikationer'"
755
+ />
756
+ @if (count() > 0) {
757
+ <ds-mobile-count-badge [count]="count()" variant="notification" />
758
+ }
759
+ `, isInline: true, styles: [":host{position:relative;display:inline-flex;align-items:center;justify-content:center}ds-icon-button{--text-color-default-secondary: var(--color-header-content, white);--text-color-default-primary: var(--color-header-content, white);--color-background-neutral-secondary-hover: rgba(var(--color-header-content-rgb, 255, 255, 255), .1);--color-background-neutral-secondary: transparent}ds-icon-button::ng-deep button{border-radius:50%!important}ds-mobile-count-badge{position:absolute;top:0;right:0;pointer-events:none}\n"], dependencies: [{ kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileCountBadgeComponent, selector: "ds-mobile-count-badge", inputs: ["count", "variant"] }] });
760
+ }
761
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationButtonComponent, decorators: [{
762
+ type: Component,
763
+ args: [{ selector: 'ds-mobile-notification-button', standalone: true, imports: [DsIconButtonComponent, DsMobileCountBadgeComponent], template: `
764
+ <ds-icon-button
765
+ icon="remixNotificationLine"
766
+ variant="secondary"
767
+ size="md"
768
+ (clicked)="clicked.emit()"
769
+ [ariaLabel]="'Notifikationer'"
770
+ />
771
+ @if (count() > 0) {
772
+ <ds-mobile-count-badge [count]="count()" variant="notification" />
773
+ }
774
+ `, styles: [":host{position:relative;display:inline-flex;align-items:center;justify-content:center}ds-icon-button{--text-color-default-secondary: var(--color-header-content, white);--text-color-default-primary: var(--color-header-content, white);--color-background-neutral-secondary-hover: rgba(var(--color-header-content-rgb, 255, 255, 255), .1);--color-background-neutral-secondary: transparent}ds-icon-button::ng-deep button{border-radius:50%!important}ds-mobile-count-badge{position:absolute;top:0;right:0;pointer-events:none}\n"] }]
775
+ }], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
776
+
725
777
  /**
726
778
  * MobilePageBase
727
779
  *
@@ -1415,10 +1467,15 @@ class DsMobileListItemComponent {
1415
1467
  }
1416
1468
 
1417
1469
  <div class="content-main">
1418
- @if (title()) {
1419
- <h3 class="structured-title">{{ title() }}</h3>
1420
- } @if (subtitle()) {
1421
- <p class="structured-subtitle">{{ subtitle() }}</p>
1470
+ @if (title() || subtitle()) {
1471
+ <div class="structured-text">
1472
+ @if (title()) {
1473
+ <h3 class="structured-title">{{ title() }}</h3>
1474
+ }
1475
+ @if (subtitle()) {
1476
+ <p class="structured-subtitle">{{ subtitle() }}</p>
1477
+ }
1478
+ </div>
1422
1479
  }
1423
1480
 
1424
1481
  <ng-content select="[content-main]" />
@@ -1440,7 +1497,7 @@ class DsMobileListItemComponent {
1440
1497
  <ng-content select="[content-trailing]" />
1441
1498
  </div>
1442
1499
  </div>
1443
- `, isInline: true, styles: [":host{display:block;position:relative;padding:var(--item-padding-top, 12px) 0 var(--item-padding-bottom, 12px) 0;box-sizing:border-box;--leading-size: 32px;--content-gap: 12px;--interactive-offset: 8px}:host(.flush-top){padding-top:0}:host:after{content:\"\";position:absolute;bottom:0;left:calc(var(--leading-size) + var(--content-gap));right:0;height:1px;background:var(--border-color-default, #e5e5e5);z-index:1;display:var(--divider-display, block)}:host(.no-divider):after{display:none}:host(.no-leading-content):after{left:0}.list-item-inner{display:flex;flex-direction:row;align-items:flex-start;gap:var(--content-gap);position:relative}:host(.align-center) .list-item-inner{align-items:center}:host(.align-bottom) .list-item-inner{align-items:flex-end}:host(.interactive) .list-item-inner:before{content:\"\";position:absolute;top:calc(-1 * var(--interactive-offset));left:calc(-1 * var(--interactive-offset));right:calc(-1 * var(--interactive-offset));bottom:calc(-1 * var(--interactive-offset));background:transparent;border-radius:16px;z-index:0;pointer-events:none}:host(.interactive){cursor:pointer;-webkit-tap-highlight-color:transparent}@media (hover: hover) and (pointer: fine){:host(.interactive):hover .list-item-inner:before{background:var(--color-background-neutral-primary-hover, #f5f5f5)}}:host(.interactive):active .list-item-inner:before{background:var(--color-background-neutral-primary-hover, #f5f5f5)}:host(.interactive):focus-visible{outline:none}:host(.interactive):focus-visible .list-item-inner:before{outline:2px solid var(--color-brand-primary, #5d5fef);outline-offset:2px}:host(.disabled){opacity:.5;pointer-events:none}:host(.loading){pointer-events:none}:host(.variant-compact) .list-item-inner{gap:8px}.content-leading{flex-shrink:0;width:var(--leading-size);height:var(--leading-size);display:flex;align-items:flex-start;justify-content:center;position:relative;z-index:1}:host(.align-center) .content-leading{align-items:center}:host(.align-bottom) .content-leading{align-items:flex-end}.content-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:8px;position:relative;z-index:1;justify-content:flex-start}:host(.align-center) .content-main{justify-content:center}:host(.align-bottom) .content-main{justify-content:flex-end}.content-trailing{flex-shrink:0;display:flex;align-items:flex-start;position:relative;z-index:1}.structured-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:600;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.structured-subtitle{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545b66);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.desktop-more-button::ng-deep button{border-radius:50%!important;box-sizing:border-box!important;width:32px!important;height:32px!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }] });
1500
+ `, isInline: true, styles: [":host{display:block;position:relative;padding:var(--item-padding-top, 12px) 0 var(--item-padding-bottom, 12px) 0;box-sizing:border-box;--leading-size: 32px;--content-gap: 12px;--interactive-offset: 8px}:host(.flush-top){padding-top:0}:host:after{content:\"\";position:absolute;bottom:0;left:calc(var(--leading-size) + var(--content-gap));right:0;height:1px;background:var(--border-color-default, #e5e5e5);z-index:1;display:var(--divider-display, block)}:host(.no-divider):after{display:none}:host(.no-leading-content):after{left:0}.list-item-inner{display:flex;flex-direction:row;align-items:flex-start;gap:var(--content-gap);position:relative}:host(.align-center) .list-item-inner{align-items:center}:host(.align-bottom) .list-item-inner{align-items:flex-end}:host(.interactive) .list-item-inner:before{content:\"\";position:absolute;top:calc(-1 * var(--interactive-offset));left:calc(-1 * var(--interactive-offset));right:calc(-1 * var(--interactive-offset));bottom:calc(-1 * var(--interactive-offset));background:transparent;border-radius:16px;z-index:0;pointer-events:none}:host(.interactive){cursor:pointer;-webkit-tap-highlight-color:transparent}@media (hover: hover) and (pointer: fine){:host(.interactive):hover .list-item-inner:before{background:var(--color-background-neutral-primary-hover, #f5f5f5)}}:host(.interactive):active .list-item-inner:before{background:var(--color-background-neutral-primary-hover, #f5f5f5)}:host(.interactive):focus-visible{outline:none}:host(.interactive):focus-visible .list-item-inner:before{outline:2px solid var(--color-brand-primary, #5d5fef);outline-offset:2px}:host(.disabled){opacity:.5;pointer-events:none}:host(.loading){pointer-events:none}:host(.variant-compact) .list-item-inner{gap:8px}.content-leading{flex-shrink:0;width:var(--leading-size);height:var(--leading-size);display:flex;align-items:flex-start;justify-content:center;position:relative;z-index:1}:host(.align-center) .content-leading{align-items:center}:host(.align-bottom) .content-leading{align-items:flex-end}.content-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:8px;position:relative;z-index:1;justify-content:flex-start}:host(.align-center) .content-main{justify-content:center}:host(.align-bottom) .content-main{justify-content:flex-end}.content-trailing{flex-shrink:0;display:flex;align-items:flex-start;position:relative;z-index:1}.structured-text{display:flex;flex-direction:column;gap:2px}.structured-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:600;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.structured-subtitle{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545b66);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.desktop-more-button::ng-deep button{border-radius:50%!important;box-sizing:border-box!important;width:32px!important;height:32px!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }] });
1444
1501
  }
1445
1502
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileListItemComponent, decorators: [{
1446
1503
  type: Component,
@@ -1479,10 +1536,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1479
1536
  }
1480
1537
 
1481
1538
  <div class="content-main">
1482
- @if (title()) {
1483
- <h3 class="structured-title">{{ title() }}</h3>
1484
- } @if (subtitle()) {
1485
- <p class="structured-subtitle">{{ subtitle() }}</p>
1539
+ @if (title() || subtitle()) {
1540
+ <div class="structured-text">
1541
+ @if (title()) {
1542
+ <h3 class="structured-title">{{ title() }}</h3>
1543
+ }
1544
+ @if (subtitle()) {
1545
+ <p class="structured-subtitle">{{ subtitle() }}</p>
1546
+ }
1547
+ </div>
1486
1548
  }
1487
1549
 
1488
1550
  <ng-content select="[content-main]" />
@@ -1504,7 +1566,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1504
1566
  <ng-content select="[content-trailing]" />
1505
1567
  </div>
1506
1568
  </div>
1507
- `, styles: [":host{display:block;position:relative;padding:var(--item-padding-top, 12px) 0 var(--item-padding-bottom, 12px) 0;box-sizing:border-box;--leading-size: 32px;--content-gap: 12px;--interactive-offset: 8px}:host(.flush-top){padding-top:0}:host:after{content:\"\";position:absolute;bottom:0;left:calc(var(--leading-size) + var(--content-gap));right:0;height:1px;background:var(--border-color-default, #e5e5e5);z-index:1;display:var(--divider-display, block)}:host(.no-divider):after{display:none}:host(.no-leading-content):after{left:0}.list-item-inner{display:flex;flex-direction:row;align-items:flex-start;gap:var(--content-gap);position:relative}:host(.align-center) .list-item-inner{align-items:center}:host(.align-bottom) .list-item-inner{align-items:flex-end}:host(.interactive) .list-item-inner:before{content:\"\";position:absolute;top:calc(-1 * var(--interactive-offset));left:calc(-1 * var(--interactive-offset));right:calc(-1 * var(--interactive-offset));bottom:calc(-1 * var(--interactive-offset));background:transparent;border-radius:16px;z-index:0;pointer-events:none}:host(.interactive){cursor:pointer;-webkit-tap-highlight-color:transparent}@media (hover: hover) and (pointer: fine){:host(.interactive):hover .list-item-inner:before{background:var(--color-background-neutral-primary-hover, #f5f5f5)}}:host(.interactive):active .list-item-inner:before{background:var(--color-background-neutral-primary-hover, #f5f5f5)}:host(.interactive):focus-visible{outline:none}:host(.interactive):focus-visible .list-item-inner:before{outline:2px solid var(--color-brand-primary, #5d5fef);outline-offset:2px}:host(.disabled){opacity:.5;pointer-events:none}:host(.loading){pointer-events:none}:host(.variant-compact) .list-item-inner{gap:8px}.content-leading{flex-shrink:0;width:var(--leading-size);height:var(--leading-size);display:flex;align-items:flex-start;justify-content:center;position:relative;z-index:1}:host(.align-center) .content-leading{align-items:center}:host(.align-bottom) .content-leading{align-items:flex-end}.content-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:8px;position:relative;z-index:1;justify-content:flex-start}:host(.align-center) .content-main{justify-content:center}:host(.align-bottom) .content-main{justify-content:flex-end}.content-trailing{flex-shrink:0;display:flex;align-items:flex-start;position:relative;z-index:1}.structured-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:600;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.structured-subtitle{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545b66);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.desktop-more-button::ng-deep button{border-radius:50%!important;box-sizing:border-box!important;width:32px!important;height:32px!important}\n"] }]
1569
+ `, styles: [":host{display:block;position:relative;padding:var(--item-padding-top, 12px) 0 var(--item-padding-bottom, 12px) 0;box-sizing:border-box;--leading-size: 32px;--content-gap: 12px;--interactive-offset: 8px}:host(.flush-top){padding-top:0}:host:after{content:\"\";position:absolute;bottom:0;left:calc(var(--leading-size) + var(--content-gap));right:0;height:1px;background:var(--border-color-default, #e5e5e5);z-index:1;display:var(--divider-display, block)}:host(.no-divider):after{display:none}:host(.no-leading-content):after{left:0}.list-item-inner{display:flex;flex-direction:row;align-items:flex-start;gap:var(--content-gap);position:relative}:host(.align-center) .list-item-inner{align-items:center}:host(.align-bottom) .list-item-inner{align-items:flex-end}:host(.interactive) .list-item-inner:before{content:\"\";position:absolute;top:calc(-1 * var(--interactive-offset));left:calc(-1 * var(--interactive-offset));right:calc(-1 * var(--interactive-offset));bottom:calc(-1 * var(--interactive-offset));background:transparent;border-radius:16px;z-index:0;pointer-events:none}:host(.interactive){cursor:pointer;-webkit-tap-highlight-color:transparent}@media (hover: hover) and (pointer: fine){:host(.interactive):hover .list-item-inner:before{background:var(--color-background-neutral-primary-hover, #f5f5f5)}}:host(.interactive):active .list-item-inner:before{background:var(--color-background-neutral-primary-hover, #f5f5f5)}:host(.interactive):focus-visible{outline:none}:host(.interactive):focus-visible .list-item-inner:before{outline:2px solid var(--color-brand-primary, #5d5fef);outline-offset:2px}:host(.disabled){opacity:.5;pointer-events:none}:host(.loading){pointer-events:none}:host(.variant-compact) .list-item-inner{gap:8px}.content-leading{flex-shrink:0;width:var(--leading-size);height:var(--leading-size);display:flex;align-items:flex-start;justify-content:center;position:relative;z-index:1}:host(.align-center) .content-leading{align-items:center}:host(.align-bottom) .content-leading{align-items:flex-end}.content-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:8px;position:relative;z-index:1;justify-content:flex-start}:host(.align-center) .content-main{justify-content:center}:host(.align-bottom) .content-main{justify-content:flex-end}.content-trailing{flex-shrink:0;display:flex;align-items:flex-start;position:relative;z-index:1}.structured-text{display:flex;flex-direction:column;gap:2px}.structured-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:600;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.structured-subtitle{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545b66);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.desktop-more-button::ng-deep button{border-radius:50%!important;box-sizing:border-box!important;width:32px!important;height:32px!important}\n"] }]
1508
1570
  }], ctorParameters: () => [], propDecorators: { leadingSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "leadingSize", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], flushTop: [{ type: i0.Input, args: [{ isSignal: true, alias: "flushTop", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], enableLongPress: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableLongPress", required: false }] }], showDesktopMoreButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDesktopMoreButton", required: false }] }], moreActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "moreActions", required: false }] }], moreButtonAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "moreButtonAriaLabel", required: false }] }], interactiveOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactiveOffset", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], showDivider: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDivider", required: false }] }], dividerSpacing: [{ type: i0.Input, args: [{ isSignal: true, alias: "dividerSpacing", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], moreButtonClick: [{ type: i0.Output, args: ["moreButtonClick"] }] } });
1509
1571
 
1510
1572
  /**
@@ -5835,6 +5897,9 @@ class DsMobilePageMainComponent extends MobilePageBase {
5835
5897
  avatarInitials = input('U', ...(ngDevMode ? [{ debugName: "avatarInitials" }] : []));
5836
5898
  avatarSrc = input('', ...(ngDevMode ? [{ debugName: "avatarSrc" }] : []));
5837
5899
  avatarIconName = input('remixUser3Line', ...(ngDevMode ? [{ debugName: "avatarIconName" }] : []));
5900
+ // Inputs - Notifications
5901
+ showNotification = input(true, ...(ngDevMode ? [{ debugName: "showNotification" }] : []));
5902
+ notificationCount = input(0, ...(ngDevMode ? [{ debugName: "notificationCount" }] : []));
5838
5903
  // Inputs - Features
5839
5904
  showRefresh = input(true, ...(ngDevMode ? [{ debugName: "showRefresh" }] : []));
5840
5905
  showCondensedHeader = input(true, ...(ngDevMode ? [{ debugName: "showCondensedHeader" }] : []));
@@ -5872,7 +5937,7 @@ class DsMobilePageMainComponent extends MobilePageBase {
5872
5937
  * {
5873
5938
  * actions: [
5874
5939
  * { action: 'profile', title: 'Min profil', icon: 'remixUser3Line' },
5875
- * { action: 'settings', title: 'Indstillinger', icon: 'remixSettings3Line' }
5940
+ * { action: 'settings', title: 'Notifikationer', icon: 'remixNotificationLine' }
5876
5941
  * ]
5877
5942
  * },
5878
5943
  * {
@@ -5885,6 +5950,7 @@ class DsMobilePageMainComponent extends MobilePageBase {
5885
5950
  */
5886
5951
  profileMenuItems = input(undefined, ...(ngDevMode ? [{ debugName: "profileMenuItems" }] : []));
5887
5952
  // Outputs
5953
+ notificationClick = output();
5888
5954
  avatarClick = output();
5889
5955
  /**
5890
5956
  * Emitted when a profile menu action is selected.
@@ -5920,8 +5986,8 @@ class DsMobilePageMainComponent extends MobilePageBase {
5920
5986
  },
5921
5987
  {
5922
5988
  action: 'settings',
5923
- title: 'Indstillinger',
5924
- icon: 'remixSettings3Line',
5989
+ title: 'Notifikationer',
5990
+ icon: 'remixNotificationLine',
5925
5991
  destructive: false,
5926
5992
  },
5927
5993
  {
@@ -6074,7 +6140,7 @@ class DsMobilePageMainComponent extends MobilePageBase {
6074
6140
  this.refresh.emit(event);
6075
6141
  }
6076
6142
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobilePageMainComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
6077
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobilePageMainComponent, isStandalone: true, selector: "ds-mobile-page-main", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, headerTitle: { classPropertyName: "headerTitle", publicName: "headerTitle", isSignal: true, isRequired: false, transformFunction: null }, headerSubtitle: { classPropertyName: "headerSubtitle", publicName: "headerSubtitle", isSignal: true, isRequired: false, transformFunction: null }, firstEntry: { classPropertyName: "firstEntry", publicName: "firstEntry", isSignal: true, isRequired: false, transformFunction: null }, avatarType: { classPropertyName: "avatarType", publicName: "avatarType", isSignal: true, isRequired: false, transformFunction: null }, avatarInitials: { classPropertyName: "avatarInitials", publicName: "avatarInitials", isSignal: true, isRequired: false, transformFunction: null }, avatarSrc: { classPropertyName: "avatarSrc", publicName: "avatarSrc", isSignal: true, isRequired: false, transformFunction: null }, avatarIconName: { classPropertyName: "avatarIconName", publicName: "avatarIconName", isSignal: true, isRequired: false, transformFunction: null }, showRefresh: { classPropertyName: "showRefresh", publicName: "showRefresh", isSignal: true, isRequired: false, transformFunction: null }, showCondensedHeader: { classPropertyName: "showCondensedHeader", publicName: "showCondensedHeader", isSignal: true, isRequired: false, transformFunction: null }, scrollThreshold: { classPropertyName: "scrollThreshold", publicName: "scrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, headerFadeDistance: { classPropertyName: "headerFadeDistance", publicName: "headerFadeDistance", isSignal: true, isRequired: false, transformFunction: null }, contentPadding: { classPropertyName: "contentPadding", publicName: "contentPadding", isSignal: true, isRequired: false, transformFunction: null }, profileMenuItems: { classPropertyName: "profileMenuItems", publicName: "profileMenuItems", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { avatarClick: "avatarClick", profileActionSelected: "profileActionSelected", refresh: "refresh", scroll: "scroll" }, host: { properties: { "style.--content-wrapper-padding": "contentPadding()" } }, viewQueries: [{ propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], usesInheritance: true, ngImport: i0, template: `
6143
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobilePageMainComponent, isStandalone: true, selector: "ds-mobile-page-main", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, headerTitle: { classPropertyName: "headerTitle", publicName: "headerTitle", isSignal: true, isRequired: false, transformFunction: null }, headerSubtitle: { classPropertyName: "headerSubtitle", publicName: "headerSubtitle", isSignal: true, isRequired: false, transformFunction: null }, firstEntry: { classPropertyName: "firstEntry", publicName: "firstEntry", isSignal: true, isRequired: false, transformFunction: null }, avatarType: { classPropertyName: "avatarType", publicName: "avatarType", isSignal: true, isRequired: false, transformFunction: null }, avatarInitials: { classPropertyName: "avatarInitials", publicName: "avatarInitials", isSignal: true, isRequired: false, transformFunction: null }, avatarSrc: { classPropertyName: "avatarSrc", publicName: "avatarSrc", isSignal: true, isRequired: false, transformFunction: null }, avatarIconName: { classPropertyName: "avatarIconName", publicName: "avatarIconName", isSignal: true, isRequired: false, transformFunction: null }, showNotification: { classPropertyName: "showNotification", publicName: "showNotification", isSignal: true, isRequired: false, transformFunction: null }, notificationCount: { classPropertyName: "notificationCount", publicName: "notificationCount", isSignal: true, isRequired: false, transformFunction: null }, showRefresh: { classPropertyName: "showRefresh", publicName: "showRefresh", isSignal: true, isRequired: false, transformFunction: null }, showCondensedHeader: { classPropertyName: "showCondensedHeader", publicName: "showCondensedHeader", isSignal: true, isRequired: false, transformFunction: null }, scrollThreshold: { classPropertyName: "scrollThreshold", publicName: "scrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, headerFadeDistance: { classPropertyName: "headerFadeDistance", publicName: "headerFadeDistance", isSignal: true, isRequired: false, transformFunction: null }, contentPadding: { classPropertyName: "contentPadding", publicName: "contentPadding", isSignal: true, isRequired: false, transformFunction: null }, profileMenuItems: { classPropertyName: "profileMenuItems", publicName: "profileMenuItems", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { notificationClick: "notificationClick", avatarClick: "avatarClick", profileActionSelected: "profileActionSelected", refresh: "refresh", scroll: "scroll" }, host: { properties: { "style.--content-wrapper-padding": "contentPadding()" } }, viewQueries: [{ propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], usesInheritance: true, ngImport: i0, template: `
6078
6144
  <!-- Fixed header at top -->
6079
6145
  <ion-header>
6080
6146
  <ion-toolbar>
@@ -6085,8 +6151,14 @@ class DsMobilePageMainComponent extends MobilePageBase {
6085
6151
  <!-- Title - fades in on scroll -->
6086
6152
  <ion-title class="header-main__title">{{ title() }}</ion-title>
6087
6153
 
6088
- <!-- Avatar -->
6154
+ <!-- Notification + Avatar -->
6089
6155
  <div class="header-main__actions">
6156
+ @if (showNotification()) {
6157
+ <ds-mobile-notification-button
6158
+ [count]="notificationCount()"
6159
+ (clicked)="notificationClick.emit()"
6160
+ />
6161
+ }
6090
6162
  <ds-avatar
6091
6163
  [size]="'md'"
6092
6164
  [type]="avatarType()"
@@ -6151,11 +6223,11 @@ class DsMobilePageMainComponent extends MobilePageBase {
6151
6223
  </div>
6152
6224
  </div>
6153
6225
  </ion-content>
6154
- `, isInline: true, styles: [":host{display:flex;flex-direction:column;align-items:center;height:100%;background:var(--color-header-surface);width:100%}:host ion-header{background:var(--color-header-surface);box-shadow:none;height:72px;min-height:72px;margin-top:var(--app-header-top-offset)}:host ion-header ion-toolbar{--background: var(--color-header-surface);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:72px;min-height:72px;padding:0}@media (min-width: 768px){:host ion-header{height:88px;min-height:88px}:host ion-header ion-toolbar{--min-height: 88px;height:88px;min-height:88px}}@media (min-width: 768px){:host ion-header{display:none;height:auto}}:host .header-main,:host .header-details,.header-details{display:flex;align-items:center;justify-content:space-between;background:var(--color-header-surface);position:relative}:host .header-main__title,:host .header-details .header-title,.header-details .header-title{position:absolute;left:50%;transform:translate(-50%);font-size:var(--font-size-base);font-weight:600;color:var(--color-header-content);margin:0;padding:0;--color: var(--color-header-content)}.header-details .header-title{transform:translate(-50%) translateY(-100%);opacity:0!important;pointer-events:none;transition:transform .2s ease,opacity .2s ease!important}.header-scrolled .header-details .header-title{opacity:1!important;pointer-events:auto;transform:translate(-50%) translateY(0)}:host .header-details .back-button,.header-details .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--color-header-content);transition:opacity var(--transition-duration-fast, .2s) var(--ease-smooth, ease);z-index:10;position:relative}:host .header-details .back-button:hover,.header-details .back-button:hover{opacity:.8}:host .header-details .back-button:active,.header-details .back-button:active{opacity:.6}:host ion-content{--background: var(--color-header-surface);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px 24px 0 0;overflow:hidden}:host ion-content::part(scroll){display:flex;flex-direction:column;min-height:100%;-webkit-overflow-scrolling:touch}.plt-ios :host ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){:host ion-content{border-radius:24px 24px 0 0}}:host ion-header[collapse=condense]{display:none}@media (min-width: 768px){:host ion-header[collapse=condense]{display:none}}:host ion-refresher{z-index:0}:host ion-refresher-content{--color: var(--color-header-content)}:host .content-wrapper{width:100%;position:relative;z-index:20;flex:1;display:flex;flex-direction:column;background:var(--color-background-neutral-primary);border-radius:24px 24px 0 0;overflow:visible;transform:translateZ(0);will-change:transform;isolation:isolate;box-shadow:0 300px 0 0 var(--color-background-neutral-primary);padding-top:0;padding-left:var(--content-wrapper-padding, 20px);padding-right:var(--content-wrapper-padding, 20px);padding-bottom:calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + var(--app-safe-bottom, 0px))}:host .content-inner{max-width:640px;margin:0 auto;width:100%}@media (min-width: 768px){:host .content-wrapper{padding-top:0;padding-left:var(--content-wrapper-padding, 20px);padding-right:var(--content-wrapper-padding, 20px)}}:host .header-expandable{background:var(--color-header-surface);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out;flex:0 0 auto;min-height:-moz-min-content;min-height:min-content}:host .header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px;margin:0 auto}:host .header-expandable__text{margin-bottom:0;gap:4px;display:flex;flex-direction:column}:host .header-expandable__title{font-size:var(--font-size-2xl);font-weight:600;color:var(--header-content-color, white);margin:0}:host .header-expandable__subtitle{font-size:var(--font-size-sm);font-weight:400;color:var(--header-content-color, white);opacity:.85;margin:0}@media (min-width: 768px){:host .header-expandable{padding:48px 20px 32px}:host .header-expandable__title{font-size:var(--font-size-3xl)}:host .header-expandable__subtitle{font-size:var(--font-size-base)}}@media (min-width: 992px){:host .header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){:host .header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){:host .header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){:host .header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}\n", ".header-main{padding:0 20px;height:72px}.header-main__title{transform:translate(-50%) translateY(-100%);opacity:0;transition:transform .6s ease,opacity .6s ease;padding:0;--color: var(--color-header-content)}.header-scrolled .header-main__title{opacity:1;transform:translate(-50%) translateY(0)}.header-main__actions{display:flex;align-items:center;gap:8px}.header-main__actions ds-avatar{cursor:pointer;-webkit-tap-highlight-color:transparent}@keyframes logoSlideIn{0%{transform:translateY(-200%);scale:.75;opacity:0}to{transform:translateY(0);scale:1;opacity:1}}.logo--first-entry{animation:logoSlideIn .75s var(--spring-curve-bouncy) .7s both}@media (min-width: 768px){.header-main{padding:16px 24px;height:88px}.header-main__title{display:none}}ion-refresher{--color: var(--color-header-content)}ion-refresher-content{--color: var(--color-header-content)}ion-refresher-content::part(spinner){color:var(--color-header-content)}\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: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }, { kind: "component", type: DsMobileGlassSpinnerComponent, selector: "ds-mobile-glass-spinner", inputs: ["spinnerSize", "borderRadius"] }] });
6226
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;align-items:center;height:100%;background:var(--color-header-surface);width:100%}:host ion-header{background:var(--color-header-surface);box-shadow:none;height:72px;min-height:72px;margin-top:var(--app-header-top-offset)}:host ion-header ion-toolbar{--background: var(--color-header-surface);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:72px;min-height:72px;padding:0}@media (min-width: 768px){:host ion-header{height:88px;min-height:88px}:host ion-header ion-toolbar{--min-height: 88px;height:88px;min-height:88px}}@media (min-width: 768px){:host ion-header{display:none;height:auto}}:host .header-main,:host .header-details,.header-details{display:flex;align-items:center;justify-content:space-between;background:var(--color-header-surface);position:relative}:host .header-main__title,:host .header-details .header-title,.header-details .header-title{position:absolute;left:50%;transform:translate(-50%);font-size:var(--font-size-base);font-weight:600;color:var(--color-header-content);margin:0;padding:0;--color: var(--color-header-content)}.header-details .header-title{transform:translate(-50%) translateY(-100%);opacity:0!important;pointer-events:none;transition:transform .2s ease,opacity .2s ease!important}.header-scrolled .header-details .header-title{opacity:1!important;pointer-events:auto;transform:translate(-50%) translateY(0)}:host .header-details .back-button,.header-details .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--color-header-content);transition:opacity var(--transition-duration-fast, .2s) var(--ease-smooth, ease);z-index:10;position:relative}:host .header-details .back-button:hover,.header-details .back-button:hover{opacity:.8}:host .header-details .back-button:active,.header-details .back-button:active{opacity:.6}:host ion-content{--background: var(--color-header-surface);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px 24px 0 0;overflow:hidden}:host ion-content::part(scroll){display:flex;flex-direction:column;min-height:100%;-webkit-overflow-scrolling:touch}.plt-ios :host ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){:host ion-content{border-radius:24px 24px 0 0}}:host ion-header[collapse=condense]{display:none}@media (min-width: 768px){:host ion-header[collapse=condense]{display:none}}:host ion-refresher{z-index:0}:host ion-refresher-content{--color: var(--color-header-content)}:host .content-wrapper{width:100%;position:relative;z-index:20;flex:1;display:flex;flex-direction:column;background:var(--color-background-neutral-primary);border-radius:24px 24px 0 0;overflow:visible;transform:translateZ(0);will-change:transform;isolation:isolate;box-shadow:0 300px 0 0 var(--color-background-neutral-primary);padding-top:0;padding-left:var(--content-wrapper-padding, 20px);padding-right:var(--content-wrapper-padding, 20px);padding-bottom:calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + var(--app-safe-bottom, 0px))}:host .content-inner{max-width:640px;margin:0 auto;width:100%}@media (min-width: 768px){:host .content-wrapper{padding-top:0;padding-left:var(--content-wrapper-padding, 20px);padding-right:var(--content-wrapper-padding, 20px)}}:host .header-expandable{background:var(--color-header-surface);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out;flex:0 0 auto;min-height:-moz-min-content;min-height:min-content}:host .header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px;margin:0 auto}:host .header-expandable__text{margin-bottom:0;gap:4px;display:flex;flex-direction:column}:host .header-expandable__title{font-size:var(--font-size-2xl);font-weight:600;color:var(--header-content-color, white);margin:0}:host .header-expandable__subtitle{font-size:var(--font-size-sm);font-weight:400;color:var(--header-content-color, white);opacity:.85;margin:0}@media (min-width: 768px){:host .header-expandable{padding:48px 20px 32px}:host .header-expandable__title{font-size:var(--font-size-3xl)}:host .header-expandable__subtitle{font-size:var(--font-size-base)}}@media (min-width: 992px){:host .header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){:host .header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){:host .header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){:host .header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}\n", ".header-main{padding:0 20px;height:72px}.header-main__title{transform:translate(-50%) translateY(-100%);opacity:0;transition:transform .6s ease,opacity .6s ease;padding:0;--color: var(--color-header-content)}.header-scrolled .header-main__title{opacity:1;transform:translate(-50%) translateY(0)}.header-main__actions{display:flex;align-items:center;gap:8px}.header-main__actions ds-avatar{cursor:pointer;-webkit-tap-highlight-color:transparent}@keyframes logoSlideIn{0%{transform:translateY(-200%);scale:.75;opacity:0}to{transform:translateY(0);scale:1;opacity:1}}.logo--first-entry{animation:logoSlideIn .75s var(--spring-curve-bouncy) .7s both}@media (min-width: 768px){.header-main{padding:16px 24px;height:88px}.header-main__title{display:none}}ion-refresher{--color: var(--color-header-content)}ion-refresher-content{--color: var(--color-header-content)}ion-refresher-content::part(spinner){color:var(--color-header-content)}\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: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }, { kind: "component", type: DsMobileGlassSpinnerComponent, selector: "ds-mobile-glass-spinner", inputs: ["spinnerSize", "borderRadius"] }, { kind: "component", type: DsMobileNotificationButtonComponent, selector: "ds-mobile-notification-button", inputs: ["count"], outputs: ["clicked"] }] });
6155
6227
  }
6156
6228
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobilePageMainComponent, decorators: [{
6157
6229
  type: Component,
6158
- args: [{ selector: 'ds-mobile-page-main', standalone: true, imports: [CommonModule, IonHeader, IonToolbar, IonTitle, IonContent, IonRefresher, IonRefresherContent, DsAvatarComponent, DsLogoComponent, DsMobileGlassSpinnerComponent], host: {
6230
+ args: [{ selector: 'ds-mobile-page-main', standalone: true, imports: [CommonModule, IonHeader, IonToolbar, IonTitle, IonContent, IonRefresher, IonRefresherContent, DsAvatarComponent, DsLogoComponent, DsMobileGlassSpinnerComponent, DsMobileNotificationButtonComponent], host: {
6159
6231
  '[style.--content-wrapper-padding]': 'contentPadding()'
6160
6232
  }, template: `
6161
6233
  <!-- Fixed header at top -->
@@ -6168,8 +6240,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6168
6240
  <!-- Title - fades in on scroll -->
6169
6241
  <ion-title class="header-main__title">{{ title() }}</ion-title>
6170
6242
 
6171
- <!-- Avatar -->
6243
+ <!-- Notification + Avatar -->
6172
6244
  <div class="header-main__actions">
6245
+ @if (showNotification()) {
6246
+ <ds-mobile-notification-button
6247
+ [count]="notificationCount()"
6248
+ (clicked)="notificationClick.emit()"
6249
+ />
6250
+ }
6173
6251
  <ds-avatar
6174
6252
  [size]="'md'"
6175
6253
  [type]="avatarType()"
@@ -6238,7 +6316,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6238
6316
  }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { ionContent: [{
6239
6317
  type: ViewChild,
6240
6318
  args: [IonContent]
6241
- }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], headerTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTitle", required: false }] }], headerSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerSubtitle", required: false }] }], firstEntry: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstEntry", required: false }] }], avatarType: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarType", required: false }] }], avatarInitials: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarInitials", required: false }] }], avatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSrc", required: false }] }], avatarIconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIconName", required: false }] }], showRefresh: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRefresh", required: false }] }], showCondensedHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCondensedHeader", required: false }] }], scrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollThreshold", required: false }] }], headerFadeDistance: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerFadeDistance", required: false }] }], contentPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentPadding", required: false }] }], profileMenuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "profileMenuItems", required: false }] }], avatarClick: [{ type: i0.Output, args: ["avatarClick"] }], profileActionSelected: [{ type: i0.Output, args: ["profileActionSelected"] }], refresh: [{ type: i0.Output, args: ["refresh"] }], scroll: [{ type: i0.Output, args: ["scroll"] }] } });
6319
+ }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], headerTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTitle", required: false }] }], headerSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerSubtitle", required: false }] }], firstEntry: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstEntry", required: false }] }], avatarType: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarType", required: false }] }], avatarInitials: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarInitials", required: false }] }], avatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSrc", required: false }] }], avatarIconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIconName", required: false }] }], showNotification: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNotification", required: false }] }], notificationCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "notificationCount", required: false }] }], showRefresh: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRefresh", required: false }] }], showCondensedHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCondensedHeader", required: false }] }], scrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollThreshold", required: false }] }], headerFadeDistance: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerFadeDistance", required: false }] }], contentPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentPadding", required: false }] }], profileMenuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "profileMenuItems", required: false }] }], notificationClick: [{ type: i0.Output, args: ["notificationClick"] }], avatarClick: [{ type: i0.Output, args: ["avatarClick"] }], profileActionSelected: [{ type: i0.Output, args: ["profileActionSelected"] }], refresh: [{ type: i0.Output, args: ["refresh"] }], scroll: [{ type: i0.Output, args: ["scroll"] }] } });
6242
6320
 
6243
6321
  /**
6244
6322
  * DsMobileInlineTabsComponent
@@ -6297,16 +6375,16 @@ class DsMobileInlineTabsComponent {
6297
6375
  }
6298
6376
  </div>
6299
6377
  } @else if (tab.badge && tab.badge > 0) {
6300
- <span class="tab-badge">{{ tab.badge }}</span>
6378
+ <ds-mobile-count-badge [count]="tab.badge" variant="muted" />
6301
6379
  }
6302
6380
  </button>
6303
6381
  }
6304
6382
  </div>
6305
- `, isInline: true, styles: [".filter-tabs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;height:40px}.filter-tab{min-width:48px;justify-content:center;padding:0 12px;height:32px;border-radius:20px;background:transparent;border:none;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.6);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;gap:6px;white-space:nowrap}.filter-tab.active{background:var(--color-header-accent, #5d5fef);color:var(--color-on-header-accent, white)}.filter-tab:hover:not(.active){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.tab-badge{min-width:24px;height:16px;padding:0 6px;border-radius:10px;background:rgba(var(--header-content-color-rgb, 255, 255, 255),.2);color:var(--header-content-color, white);font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:600;display:flex;align-items:center;justify-content:center;line-height:1}.filter-tab.active .tab-badge{background:rgba(var(--color-on-header-accent-rgb, 255, 255, 255),.3);color:var(--color-on-header-accent, white)}.filter-tab.has-envelope{gap:0}.envelope-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center;margin-left:4px}.envelope-badge{position:absolute;top:-3px;right:-5px;background-color:#ff3b30;color:#fff;font-size:10px;font-weight:700;border-radius:9999px;min-width:12px;height:12px;display:flex;align-items:center;justify-content:center;padding:0;line-height:1;box-shadow:0 1px 2px #00000026;z-index:10}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }] });
6383
+ `, isInline: true, styles: [".filter-tabs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;height:40px}.filter-tab{min-width:48px;justify-content:center;padding:0 12px;height:32px;border-radius:20px;background:transparent;border:none;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.6);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;gap:6px;white-space:nowrap}.filter-tab.active{background:var(--color-header-accent, #5d5fef);color:var(--color-on-header-accent, white)}.filter-tab:hover:not(.active){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.filter-tab.active ds-mobile-count-badge{--header-content-color-rgb: var(--color-on-header-accent-rgb, 255, 255, 255);--header-content-color: var(--color-on-header-accent, white)}.filter-tab.has-envelope{gap:0}.envelope-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center;margin-left:4px}.envelope-badge{position:absolute;top:-3px;right:-5px;background-color:#ff3b30;color:#fff;font-size:10px;font-weight:700;border-radius:9999px;min-width:12px;height:12px;display:flex;align-items:center;justify-content:center;padding:0;line-height:1;box-shadow:0 1px 2px #00000026;z-index:10}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobileCountBadgeComponent, selector: "ds-mobile-count-badge", inputs: ["count", "variant"] }] });
6306
6384
  }
6307
6385
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileInlineTabsComponent, decorators: [{
6308
6386
  type: Component,
6309
- args: [{ selector: 'ds-mobile-inline-tabs', standalone: true, imports: [CommonModule, DsIconComponent], template: `
6387
+ args: [{ selector: 'ds-mobile-inline-tabs', standalone: true, imports: [CommonModule, DsIconComponent, DsMobileCountBadgeComponent], template: `
6310
6388
  <div class="filter-tabs">
6311
6389
  @for (tab of tabs(); track tab.id) {
6312
6390
  <button
@@ -6327,12 +6405,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6327
6405
  }
6328
6406
  </div>
6329
6407
  } @else if (tab.badge && tab.badge > 0) {
6330
- <span class="tab-badge">{{ tab.badge }}</span>
6408
+ <ds-mobile-count-badge [count]="tab.badge" variant="muted" />
6331
6409
  }
6332
6410
  </button>
6333
6411
  }
6334
6412
  </div>
6335
- `, styles: [".filter-tabs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;height:40px}.filter-tab{min-width:48px;justify-content:center;padding:0 12px;height:32px;border-radius:20px;background:transparent;border:none;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.6);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;gap:6px;white-space:nowrap}.filter-tab.active{background:var(--color-header-accent, #5d5fef);color:var(--color-on-header-accent, white)}.filter-tab:hover:not(.active){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.tab-badge{min-width:24px;height:16px;padding:0 6px;border-radius:10px;background:rgba(var(--header-content-color-rgb, 255, 255, 255),.2);color:var(--header-content-color, white);font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:600;display:flex;align-items:center;justify-content:center;line-height:1}.filter-tab.active .tab-badge{background:rgba(var(--color-on-header-accent-rgb, 255, 255, 255),.3);color:var(--color-on-header-accent, white)}.filter-tab.has-envelope{gap:0}.envelope-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center;margin-left:4px}.envelope-badge{position:absolute;top:-3px;right:-5px;background-color:#ff3b30;color:#fff;font-size:10px;font-weight:700;border-radius:9999px;min-width:12px;height:12px;display:flex;align-items:center;justify-content:center;padding:0;line-height:1;box-shadow:0 1px 2px #00000026;z-index:10}\n"] }]
6413
+ `, styles: [".filter-tabs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;height:40px}.filter-tab{min-width:48px;justify-content:center;padding:0 12px;height:32px;border-radius:20px;background:transparent;border:none;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.6);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;gap:6px;white-space:nowrap}.filter-tab.active{background:var(--color-header-accent, #5d5fef);color:var(--color-on-header-accent, white)}.filter-tab:hover:not(.active){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.filter-tab.active ds-mobile-count-badge{--header-content-color-rgb: var(--color-on-header-accent-rgb, 255, 255, 255);--header-content-color: var(--color-on-header-accent, white)}.filter-tab.has-envelope{gap:0}.envelope-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center;margin-left:4px}.envelope-badge{position:absolute;top:-3px;right:-5px;background-color:#ff3b30;color:#fff;font-size:10px;font-weight:700;border-radius:9999px;min-width:12px;height:12px;display:flex;align-items:center;justify-content:center;padding:0;line-height:1;box-shadow:0 1px 2px #00000026;z-index:10}\n"] }]
6336
6414
  }], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: true }] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }] } });
6337
6415
 
6338
6416
  /**
@@ -11756,7 +11834,7 @@ class DsMobileModalBaseComponent extends MobileModalBase {
11756
11834
  this.headerMeta() ||
11757
11835
  this.hasContentInSlot(this.headerLeading) ||
11758
11836
  this.hasContentInSlot(this.headerMain) ||
11759
- this.hasContentInSlot(this.headerTrailing));
11837
+ !!this.headerTrailing);
11760
11838
  }
11761
11839
  /**
11762
11840
  * Check whether header-leading slot has actual projected content.
@@ -11764,6 +11842,9 @@ class DsMobileModalBaseComponent extends MobileModalBase {
11764
11842
  hasHeaderLeadingContent() {
11765
11843
  return this.hasContentInSlot(this.headerLeading);
11766
11844
  }
11845
+ hasHeaderTrailingContent() {
11846
+ return !!this.headerTrailing;
11847
+ }
11767
11848
  /**
11768
11849
  * Check if a content child slot has actual content
11769
11850
  */
@@ -19246,11 +19327,14 @@ class DsMobileTabBarComponent {
19246
19327
  * ```
19247
19328
  */
19248
19329
  profileMenuItems;
19330
+ // Notification inputs
19331
+ notificationCount = 0;
19249
19332
  moreMenuItems = [];
19250
19333
  moreMenuLabel = 'Mere';
19251
19334
  moreMenuIcon = 'remixGridLine';
19252
19335
  moreMenuIconActive = 'remixGridFill';
19253
19336
  // Outputs
19337
+ notificationClick = new EventEmitter();
19254
19338
  avatarClick = new EventEmitter();
19255
19339
  /**
19256
19340
  * Emitted when a profile menu action is selected.
@@ -19836,7 +19920,7 @@ class DsMobileTabBarComponent {
19836
19920
  }
19837
19921
  }
19838
19922
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileTabBarComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
19839
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileTabBarComponent, isStandalone: true, selector: "ds-mobile-tab-bar", inputs: { tabs: "tabs", avatarType: "avatarType", avatarInitials: "avatarInitials", avatarSrc: "avatarSrc", avatarIconName: "avatarIconName", profileMenuItems: "profileMenuItems", moreMenuItems: "moreMenuItems", moreMenuLabel: "moreMenuLabel", moreMenuIcon: "moreMenuIcon", moreMenuIconActive: "moreMenuIconActive" }, outputs: { avatarClick: "avatarClick", profileActionSelected: "profileActionSelected", moreMenuItemSelected: "moreMenuItemSelected" }, ngImport: i0, template: `
19923
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileTabBarComponent, isStandalone: true, selector: "ds-mobile-tab-bar", inputs: { tabs: "tabs", avatarType: "avatarType", avatarInitials: "avatarInitials", avatarSrc: "avatarSrc", avatarIconName: "avatarIconName", profileMenuItems: "profileMenuItems", notificationCount: "notificationCount", moreMenuItems: "moreMenuItems", moreMenuLabel: "moreMenuLabel", moreMenuIcon: "moreMenuIcon", moreMenuIconActive: "moreMenuIconActive" }, outputs: { notificationClick: "notificationClick", avatarClick: "avatarClick", profileActionSelected: "profileActionSelected", moreMenuItemSelected: "moreMenuItemSelected" }, ngImport: i0, template: `
19840
19924
  <ion-tab-bar [attr.slot]="isDesktop() ? 'top' : 'bottom'" class="ds-tab-bar" [class.ds-tab-bar--desktop]="isDesktop()">
19841
19925
  <!-- Logo (desktop only, full logo in header) -->
19842
19926
  <div class="ds-tab-bar__logo">
@@ -19879,16 +19963,20 @@ class DsMobileTabBarComponent {
19879
19963
  }
19880
19964
  </div>
19881
19965
 
19882
- <!-- Avatar (desktop only, positioned via CSS) -->
19966
+ <!-- Notification + Avatar (desktop only, positioned via CSS) -->
19883
19967
  <div class="ds-tab-bar__actions">
19968
+ <ds-mobile-notification-button
19969
+ [count]="notificationCount"
19970
+ (clicked)="notificationClick.emit()"
19971
+ />
19884
19972
  <ds-avatar [size]="'md'" [type]="avatarType" [initials]="avatarInitials" [src]="avatarSrc" [iconName]="avatarIconName" (click)="handleAvatarClick()" />
19885
19973
  </div>
19886
19974
  </ion-tab-bar>
19887
- `, isInline: true, styles: [":host{--ds-tab-bar-height: 64px}@media (min-width: 768px){:host{display:block;--ds-tab-bar-height: 64px}}@media (max-width: 767px){:host{display:contents}}ion-tabs.ds-tabs-wrapper{height:100%;background:var(--color-header-surface)}ion-tab-button:before,ion-tab-button:after{content:none!important;display:none!important}ion-tab-button[title]:before,ion-tab-button[title]:after{display:none!important}ion-tab-button::part(native):before,ion-tab-button::part(native):after{display:none!important}.ds-tab-bar{--background: var(--color-background-neutral-primary);transition:transform .2s ease-in-out}ion-tab-bar[slot=bottom]{border-top:1px solid var(--border-color-default);padding-top:8px;padding-bottom:max(8px,calc(var(--app-safe-bottom, 0px) - 16px));padding-left:12px;padding-right:12px}@media (max-width: 767px){ion-tab-bar[slot=bottom]{position:fixed;bottom:0;left:0;right:0;z-index:100}}@media (max-width: 767px){:host-context(.plt-android) ion-tab-bar[slot=bottom]{padding-bottom:max(8px,var(--app-safe-bottom, 0px))!important}}@media (max-width: 767px){:host-context(ion-tabs:has(ds-mobile-page-details)) .ds-tab-bar{transform:translateY(100%);transition:transform .3s ease}}.ds-tab-bar__logo,.ds-tab-bar__actions{display:none}.ds-tab-bar__tabs{display:flex;width:100%;justify-content:space-around;align-items:center}@media (min-width: 769px){ion-tab-bar[slot=bottom]{padding-bottom:var(--app-safe-bottom, 0px)}}@media (display-mode: standalone){ion-tab-bar[slot=bottom]{padding-bottom:var(--app-safe-bottom, 0px)!important}}@media (min-width: 768px){:host[slot=top]{order:-1!important}:host ion-tab-bar{position:relative!important;bottom:auto!important;top:0!important}ion-tab-bar[slot=top]{--background: var(--color-header-surface);position:relative!important;display:flex!important;align-items:center;padding:12px 24px;height:64px;max-width:none;bottom:auto!important;top:0!important}ion-tab-bar[slot=bottom]{position:relative!important;bottom:auto!important}ion-tabs>div:not([slot]){order:1!important}.ds-tab-bar__logo{display:flex;position:absolute;left:24px;align-items:center;color:var(--header-content-color, white)}.ds-tab-bar__actions{display:flex;position:absolute;right:24px;align-items:center;gap:12px}.ds-tab-bar__tabs{display:flex;gap:8px;align-items:center;max-width:640px;width:100%;margin:0 auto;justify-content:center;padding-left:var(--content-padding-md);padding-right:var(--content-padding-md)}.logomark{height:28px;width:auto;flex-shrink:0}}@media (min-width: 992px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg);justify-content:center}}@media (min-width: 1440px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-tab-button{--color: var(--text-color-default-tertiary);--color-selected: var(--color-accent);--padding-start: 0;--padding-end: 0;--ripple-color: transparent;display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:visible;pointer-events:auto}ion-tab-button[title]:before{content:attr(title);position:absolute;opacity:0;pointer-events:none}.tab-icon-ripple{position:absolute;left:50%;top:50%;width:40px;height:40px;border-radius:50%;background:var(--color-accent);transform:translate(-50%,-50%) scale(0);opacity:0;pointer-events:none;transition:all .6s cubic-bezier(.36,1.2,.04,1.4);z-index:0}.tab-selected .tab-icon-ripple{transform:translate(-50%,-50%) scale(2);opacity:.05;animation:ripple-fade .6s cubic-bezier(.36,.5,.04,1.8) forwards}@keyframes ripple-fade{0%{opacity:0;transform:translate(-50%,-50%) scale(0)}30%{opacity:.1}to{opacity:0;transform:translate(-50%,-50%) scale(2)}}ion-tab-button::part(native){overflow:visible}ion-tab-button ion-ripple-effect{color:var(--color-accent);border-radius:1000px}ion-tab-button ion-label{font-size:11px;font-weight:500;letter-spacing:-.5px;margin-top:0}.tab-icon-wrapper{position:relative;width:24px;height:24px;display:flex;align-items:center;justify-content:center;z-index:1;margin-bottom:4px}.tab-icon-inactive,.tab-icon-active{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);transition:all .8s cubic-bezier(.36,1,.04,1)}.tab-icon-inactive{opacity:1;transform:translate(-50%,-50%) scale(1)}.tab-icon-active{opacity:0;transform:translate(-50%,calc(-50% - 12px)) scale(.5)}.tab-selected .tab-icon-inactive{opacity:0;transform:translate(-50%,-50%) scale(.5)}.tab-selected .tab-icon-active{opacity:1;transform:translate(-50%,-50%) scale(1)}@media (min-width: 768px){.ds-tab-button{flex-direction:row;height:40px;padding:0 16px!important;border-radius:40px;transition:all .2s ease;width:-moz-fit-content;width:fit-content;min-width:-moz-fit-content;min-width:fit-content;flex:0 0 auto;--color: rgba(var(--color-header-content-rgb, 255, 255, 255), .7);--color-selected: var(--color-header-content, white);color:rgba(var(--color-header-content-rgb, 255, 255, 255),.7);background:transparent;position:relative;overflow:hidden}.tab-icon-wrapper,.tab-icon-ripple{width:20px;height:20px}.ds-tab-button::part(native){border-radius:40px}.ds-tab-button:hover:not(.tab-selected){--color: var(--color-header-content, white);--color-selected: var(--color-header-content, white);color:var(--color-header-content, white);background:rgba(var(--color-header-content-rgb, 255, 255, 255),.1)}.ds-tab-button:hover:not(.tab-selected) ion-label{color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon{--icon-color: var(--color-header-content, white);color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon svg{fill:var(--color-header-content, white)}.ds-tab-button.tab-selected,.ds-tab-button.tab-selected:hover{background:var(--color-header-accent);--color-selected: var(--color-on-header-accent, white);--color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover .tab-icon-active{opacity:1!important;visibility:visible!important}.ds-tab-button.tab-selected:hover .tab-icon-inactive{opacity:0!important}.ds-tab-button.tab-selected ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button .button-native{width:auto;padding:0}.ds-tab-button ion-label{font-size:var(--font-size-sm);font-weight:500;margin:0;color:inherit}.ds-tab-button .tab-icon-wrapper{margin-right:4px;margin-bottom:0}.ds-tab-button ion-ripple-effect{color:rgba(var(--header-content-color-rgb, 255, 255, 255),.3);border-radius:1000px;transform:scale(1.5)}}@media (min-width: 768px){.ds-tab-bar__actions ds-avatar{cursor:pointer;transition:transform .2s ease}.ds-tab-bar__actions ds-avatar:hover{transform:scale(1.05)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabBar, selector: "ion-tab-bar", inputs: ["color", "mode", "selectedTab", "translucent"] }, { kind: "component", type: IonTabButton, selector: "ion-tab-button", inputs: ["disabled", "download", "href", "layout", "mode", "rel", "selected", "tab", "target"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }] });
19975
+ `, isInline: true, styles: [":host{--ds-tab-bar-height: 64px}@media (min-width: 768px){:host{display:block;--ds-tab-bar-height: 64px}}@media (max-width: 767px){:host{display:contents}}ion-tabs.ds-tabs-wrapper{height:100%;background:var(--color-header-surface)}ion-tab-button:before,ion-tab-button:after{content:none!important;display:none!important}ion-tab-button[title]:before,ion-tab-button[title]:after{display:none!important}ion-tab-button::part(native):before,ion-tab-button::part(native):after{display:none!important}.ds-tab-bar{--background: var(--color-background-neutral-primary);transition:transform .2s ease-in-out}ion-tab-bar[slot=bottom]{border-top:1px solid var(--border-color-default);padding-top:8px;padding-bottom:max(8px,calc(var(--app-safe-bottom, 0px) - 16px));padding-left:12px;padding-right:12px}@media (max-width: 767px){ion-tab-bar[slot=bottom]{position:fixed;bottom:0;left:0;right:0;z-index:100}}@media (max-width: 767px){:host-context(.plt-android) ion-tab-bar[slot=bottom]{padding-bottom:max(8px,var(--app-safe-bottom, 0px))!important}}@media (max-width: 767px){:host-context(ion-tabs:has(ds-mobile-page-details)) .ds-tab-bar{transform:translateY(100%);transition:transform .3s ease}}.ds-tab-bar__logo,.ds-tab-bar__actions{display:none}.ds-tab-bar__tabs{display:flex;width:100%;justify-content:space-around;align-items:center}@media (min-width: 769px){ion-tab-bar[slot=bottom]{padding-bottom:var(--app-safe-bottom, 0px)}}@media (display-mode: standalone){ion-tab-bar[slot=bottom]{padding-bottom:var(--app-safe-bottom, 0px)!important}}@media (min-width: 768px){:host[slot=top]{order:-1!important}:host ion-tab-bar{position:relative!important;bottom:auto!important;top:0!important}ion-tab-bar[slot=top]{--background: var(--color-header-surface);position:relative!important;display:flex!important;align-items:center;padding:12px 24px;height:64px;max-width:none;bottom:auto!important;top:0!important}ion-tab-bar[slot=bottom]{position:relative!important;bottom:auto!important}ion-tabs>div:not([slot]){order:1!important}.ds-tab-bar__logo{display:flex;position:absolute;left:24px;align-items:center;color:var(--header-content-color, white)}.ds-tab-bar__actions{display:flex;position:absolute;right:24px;align-items:center;gap:12px}.ds-tab-bar__tabs{display:flex;gap:8px;align-items:center;max-width:640px;width:100%;margin:0 auto;justify-content:center;padding-left:var(--content-padding-md);padding-right:var(--content-padding-md)}.logomark{height:28px;width:auto;flex-shrink:0}}@media (min-width: 992px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg);justify-content:center}}@media (min-width: 1440px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-tab-button{--color: var(--text-color-default-tertiary);--color-selected: var(--color-accent);--padding-start: 0;--padding-end: 0;--ripple-color: transparent;display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:visible;pointer-events:auto}ion-tab-button[title]:before{content:attr(title);position:absolute;opacity:0;pointer-events:none}.tab-icon-ripple{position:absolute;left:50%;top:50%;width:40px;height:40px;border-radius:50%;background:var(--color-accent);transform:translate(-50%,-50%) scale(0);opacity:0;pointer-events:none;transition:all .6s cubic-bezier(.36,1.2,.04,1.4);z-index:0}.tab-selected .tab-icon-ripple{transform:translate(-50%,-50%) scale(2);opacity:.05;animation:ripple-fade .6s cubic-bezier(.36,.5,.04,1.8) forwards}@keyframes ripple-fade{0%{opacity:0;transform:translate(-50%,-50%) scale(0)}30%{opacity:.1}to{opacity:0;transform:translate(-50%,-50%) scale(2)}}ion-tab-button::part(native){overflow:visible}ion-tab-button ion-ripple-effect{color:var(--color-accent);border-radius:1000px}ion-tab-button ion-label{font-size:11px;font-weight:500;letter-spacing:-.5px;margin-top:0}.tab-icon-wrapper{position:relative;width:24px;height:24px;display:flex;align-items:center;justify-content:center;z-index:1;margin-bottom:4px}.tab-icon-inactive,.tab-icon-active{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);transition:all .8s cubic-bezier(.36,1,.04,1)}.tab-icon-inactive{opacity:1;transform:translate(-50%,-50%) scale(1)}.tab-icon-active{opacity:0;transform:translate(-50%,calc(-50% - 12px)) scale(.5)}.tab-selected .tab-icon-inactive{opacity:0;transform:translate(-50%,-50%) scale(.5)}.tab-selected .tab-icon-active{opacity:1;transform:translate(-50%,-50%) scale(1)}@media (min-width: 768px){.ds-tab-button{flex-direction:row;height:40px;padding:0 16px!important;border-radius:40px;transition:all .2s ease;width:-moz-fit-content;width:fit-content;min-width:-moz-fit-content;min-width:fit-content;flex:0 0 auto;--color: rgba(var(--color-header-content-rgb, 255, 255, 255), .7);--color-selected: var(--color-header-content, white);color:rgba(var(--color-header-content-rgb, 255, 255, 255),.7);background:transparent;position:relative;overflow:hidden}.tab-icon-wrapper,.tab-icon-ripple{width:20px;height:20px}.ds-tab-button::part(native){border-radius:40px}.ds-tab-button:hover:not(.tab-selected){--color: var(--color-header-content, white);--color-selected: var(--color-header-content, white);color:var(--color-header-content, white);background:rgba(var(--color-header-content-rgb, 255, 255, 255),.1)}.ds-tab-button:hover:not(.tab-selected) ion-label{color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon{--icon-color: var(--color-header-content, white);color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon svg{fill:var(--color-header-content, white)}.ds-tab-button.tab-selected,.ds-tab-button.tab-selected:hover{background:var(--color-header-accent);--color-selected: var(--color-on-header-accent, white);--color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover .tab-icon-active{opacity:1!important;visibility:visible!important}.ds-tab-button.tab-selected:hover .tab-icon-inactive{opacity:0!important}.ds-tab-button.tab-selected ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button .button-native{width:auto;padding:0}.ds-tab-button ion-label{font-size:var(--font-size-sm);font-weight:500;margin:0;color:inherit}.ds-tab-button .tab-icon-wrapper{margin-right:4px;margin-bottom:0}.ds-tab-button ion-ripple-effect{color:rgba(var(--header-content-color-rgb, 255, 255, 255),.3);border-radius:1000px;transform:scale(1.5)}}@media (min-width: 768px){.ds-tab-bar__actions ds-avatar{cursor:pointer;transition:transform .2s ease}.ds-tab-bar__actions ds-avatar:hover{transform:scale(1.05)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabBar, selector: "ion-tab-bar", inputs: ["color", "mode", "selectedTab", "translucent"] }, { kind: "component", type: IonTabButton, selector: "ion-tab-button", inputs: ["disabled", "download", "href", "layout", "mode", "rel", "selected", "tab", "target"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }, { kind: "component", type: DsMobileNotificationButtonComponent, selector: "ds-mobile-notification-button", inputs: ["count"], outputs: ["clicked"] }] });
19888
19976
  }
19889
19977
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileTabBarComponent, decorators: [{
19890
19978
  type: Component,
19891
- args: [{ selector: 'ds-mobile-tab-bar', standalone: true, imports: [CommonModule, IonTabBar, IonTabButton, IonLabel, DsIconComponent, DsAvatarComponent, DsLogoComponent], template: `
19979
+ args: [{ selector: 'ds-mobile-tab-bar', standalone: true, imports: [CommonModule, IonTabBar, IonTabButton, IonLabel, DsIconComponent, DsAvatarComponent, DsLogoComponent, DsMobileNotificationButtonComponent], template: `
19892
19980
  <ion-tab-bar [attr.slot]="isDesktop() ? 'top' : 'bottom'" class="ds-tab-bar" [class.ds-tab-bar--desktop]="isDesktop()">
19893
19981
  <!-- Logo (desktop only, full logo in header) -->
19894
19982
  <div class="ds-tab-bar__logo">
@@ -19931,8 +20019,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
19931
20019
  }
19932
20020
  </div>
19933
20021
 
19934
- <!-- Avatar (desktop only, positioned via CSS) -->
20022
+ <!-- Notification + Avatar (desktop only, positioned via CSS) -->
19935
20023
  <div class="ds-tab-bar__actions">
20024
+ <ds-mobile-notification-button
20025
+ [count]="notificationCount"
20026
+ (clicked)="notificationClick.emit()"
20027
+ />
19936
20028
  <ds-avatar [size]="'md'" [type]="avatarType" [initials]="avatarInitials" [src]="avatarSrc" [iconName]="avatarIconName" (click)="handleAvatarClick()" />
19937
20029
  </div>
19938
20030
  </ion-tab-bar>
@@ -19949,6 +20041,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
19949
20041
  type: Input
19950
20042
  }], profileMenuItems: [{
19951
20043
  type: Input
20044
+ }], notificationCount: [{
20045
+ type: Input
19952
20046
  }], moreMenuItems: [{
19953
20047
  type: Input
19954
20048
  }], moreMenuLabel: [{
@@ -19957,6 +20051,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
19957
20051
  type: Input
19958
20052
  }], moreMenuIconActive: [{
19959
20053
  type: Input
20054
+ }], notificationClick: [{
20055
+ type: Output
19960
20056
  }], avatarClick: [{
19961
20057
  type: Output
19962
20058
  }], profileActionSelected: [{
@@ -20025,7 +20121,7 @@ class DsMobileTabsComponent {
20025
20121
  (avatarClick)="handleAvatarClick()"
20026
20122
  />
20027
20123
  </ion-tabs>
20028
- `, isInline: true, styles: [":host{display:block;height:100vh;height:100dvh}ion-tabs{height:100%;background:var(--color-header-surface)}@media (max-width: 767px){ion-tabs:has(ds-mobile-page-details) ds-mobile-tab-bar{--tab-bar-transform: translateY(100%)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabs, selector: "ion-tabs" }, { kind: "component", type: IonTab, selector: "ion-tab", inputs: ["component", "tab"] }, { kind: "component", type: DsMobileTabBarComponent, selector: "ds-mobile-tab-bar", inputs: ["tabs", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "profileMenuItems", "moreMenuItems", "moreMenuLabel", "moreMenuIcon", "moreMenuIconActive"], outputs: ["avatarClick", "profileActionSelected", "moreMenuItemSelected"] }] });
20124
+ `, isInline: true, styles: [":host{display:block;height:100vh;height:100dvh}ion-tabs{height:100%;background:var(--color-header-surface)}@media (max-width: 767px){ion-tabs:has(ds-mobile-page-details) ds-mobile-tab-bar{--tab-bar-transform: translateY(100%)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabs, selector: "ion-tabs" }, { kind: "component", type: IonTab, selector: "ion-tab", inputs: ["component", "tab"] }, { kind: "component", type: DsMobileTabBarComponent, selector: "ds-mobile-tab-bar", inputs: ["tabs", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "profileMenuItems", "notificationCount", "moreMenuItems", "moreMenuLabel", "moreMenuIcon", "moreMenuIconActive"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "moreMenuItemSelected"] }] });
20029
20125
  }
20030
20126
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileTabsComponent, decorators: [{
20031
20127
  type: Component,
@@ -21806,7 +21902,7 @@ class DsMobileEmptyStateComponent {
21806
21902
  }
21807
21903
  </div>
21808
21904
  </div>
21809
- `, isInline: true, styles: [":host{display:block}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;gap:24px}.empty-state-image{width:96px;height:96px;margin:0 auto}.empty-state-text{display:flex;flex-direction:column;gap:8px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
21905
+ `, isInline: true, styles: [":host{display:block}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;gap:24px}.empty-state-image{width:120px;height:120px;margin:0 auto}.empty-state-text{display:flex;flex-direction:column;gap:8px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
21810
21906
  }
21811
21907
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileEmptyStateComponent, decorators: [{
21812
21908
  type: Component,
@@ -21825,7 +21921,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
21825
21921
  }
21826
21922
  </div>
21827
21923
  </div>
21828
- `, styles: [":host{display:block}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;gap:24px}.empty-state-image{width:96px;height:96px;margin:0 auto}.empty-state-text{display:flex;flex-direction:column;gap:8px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}\n"] }]
21924
+ `, styles: [":host{display:block}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;gap:24px}.empty-state-image{width:120px;height:120px;margin:0 auto}.empty-state-text{display:flex;flex-direction:column;gap:8px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}\n"] }]
21829
21925
  }], propDecorators: { imageSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageSrc", required: false }] }], imageAlt: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageAlt", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }] } });
21830
21926
 
21831
21927
  /**
@@ -22702,16 +22798,16 @@ class DsMobileCardInlineBannerComponent {
22702
22798
 
22703
22799
  <div content-trailing class="item-trailing">
22704
22800
  @if (unreadCount() && unreadCount()! > 0) {
22705
- <span class="unread-badge">{{ unreadCount() }}</span>
22801
+ <ds-mobile-count-badge [count]="unreadCount()!" variant="accent" />
22706
22802
  }
22707
22803
  <ds-icon name="remixArrowRightSLine" size="20px" />
22708
22804
  </div>
22709
22805
  </ds-mobile-card-inline>
22710
- `, isInline: true, styles: [".unread-badge{min-width:24px;height:16px;padding:0 6px;border-radius:10px;background:var(--color-accent, #6B5FF5);color:var(--color-on-accent, #ffffff);font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:600;display:flex;align-items:center;justify-content:center;line-height:1;margin-right:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsMobileCardInlineComponent, selector: "ds-mobile-card-inline", inputs: ["variant", "disabled"], outputs: ["cardClick"] }] });
22806
+ `, isInline: true, styles: ["ds-mobile-count-badge{margin-right:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsMobileCardInlineComponent, selector: "ds-mobile-card-inline", inputs: ["variant", "disabled"], outputs: ["cardClick"] }, { kind: "component", type: DsMobileCountBadgeComponent, selector: "ds-mobile-count-badge", inputs: ["count", "variant"] }] });
22711
22807
  }
22712
22808
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileCardInlineBannerComponent, decorators: [{
22713
22809
  type: Component,
22714
- args: [{ selector: 'ds-mobile-card-inline-banner', standalone: true, imports: [CommonModule, DsIconComponent, DsAvatarComponent, DsMobileCardInlineComponent], template: `
22810
+ args: [{ selector: 'ds-mobile-card-inline-banner', standalone: true, imports: [CommonModule, DsIconComponent, DsAvatarComponent, DsMobileCardInlineComponent, DsMobileCountBadgeComponent], template: `
22715
22811
  <ds-mobile-card-inline
22716
22812
  [variant]="layout()"
22717
22813
  (cardClick)="handleBannerClick()">
@@ -22736,12 +22832,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
22736
22832
 
22737
22833
  <div content-trailing class="item-trailing">
22738
22834
  @if (unreadCount() && unreadCount()! > 0) {
22739
- <span class="unread-badge">{{ unreadCount() }}</span>
22835
+ <ds-mobile-count-badge [count]="unreadCount()!" variant="accent" />
22740
22836
  }
22741
22837
  <ds-icon name="remixArrowRightSLine" size="20px" />
22742
22838
  </div>
22743
22839
  </ds-mobile-card-inline>
22744
- `, styles: [".unread-badge{min-width:24px;height:16px;padding:0 6px;border-radius:10px;background:var(--color-accent, #6B5FF5);color:var(--color-on-accent, #ffffff);font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:600;display:flex;align-items:center;justify-content:center;line-height:1;margin-right:8px}\n"] }]
22840
+ `, styles: ["ds-mobile-count-badge{margin-right:8px}\n"] }]
22745
22841
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], timestamp: [{ type: i0.Input, args: [{ isSignal: true, alias: "timestamp", required: false }] }], unreadCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "unreadCount", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], bannerClick: [{ type: i0.Output, args: ["bannerClick"] }] } });
22746
22842
 
22747
22843
  /**
@@ -26092,7 +26188,7 @@ class DsMobilePriceSheetComponent {
26092
26188
  inputmode="numeric"
26093
26189
  pattern="[0-9]*"
26094
26190
  maxlength="5"
26095
- [attr.size]="Math.min(customPrice()?.length || 2, 5)"
26191
+ [attr.size]="Math.min(customPrice().length || 2, 5)"
26096
26192
  [(ngModel)]="customPrice"
26097
26193
  placeholder="75"
26098
26194
  class="price-input-native"
@@ -26147,7 +26243,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
26147
26243
  inputmode="numeric"
26148
26244
  pattern="[0-9]*"
26149
26245
  maxlength="5"
26150
- [attr.size]="Math.min(customPrice()?.length || 2, 5)"
26246
+ [attr.size]="Math.min(customPrice().length || 2, 5)"
26151
26247
  [(ngModel)]="customPrice"
26152
26248
  placeholder="75"
26153
26249
  class="price-input-native"
@@ -28624,7 +28720,755 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
28624
28720
  `, styles: [":host{display:block;width:100%}.property-banner{display:flex;align-items:center;gap:8px;height:44px;padding:4px 10px 4px 6px;background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1);border:none;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:12px;cursor:pointer;transition:background .2s ease;-webkit-tap-highlight-color:transparent}.property-banner:hover{background:rgba(var(--header-content-color-rgb, 255, 255, 255),.12)}.property-banner:active{background:rgba(var(--header-content-color-rgb, 255, 255, 255),.15)}.property-photo{width:32px;height:32px;border-radius:8px;object-fit:cover;flex-shrink:0;background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.property-text{display:flex;flex-direction:column;flex:1;min-width:0}.property-address{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--header-content-color, white);line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.property-meta{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.65);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.property-chevron{flex-shrink:0;width:16px;height:16px;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.5)}\n"] }]
28625
28721
  }], propDecorators: { address: [{ type: i0.Input, args: [{ isSignal: true, alias: "address", required: true }] }], photoUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "photoUrl", required: true }] }], tenantCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "tenantCount", required: false }] }] } });
28626
28722
 
28627
- // Mobile Page Components
28723
+ class DsMobileNotificationPromptComponent {
28724
+ whitelabel = inject(WhitelabelService);
28725
+ dismissing = signal(false, ...(ngDevMode ? [{ debugName: "dismissing" }] : []));
28726
+ heading = input('Vær den første til at vide det', ...(ngDevMode ? [{ debugName: "heading" }] : []));
28727
+ subtitle = input('Hold dig opdateret med henvendelser, bookinger, nyheder og alt derimellem.', ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
28728
+ allowLabel = input('Slå notifikationer til', ...(ngDevMode ? [{ debugName: "allowLabel" }] : []));
28729
+ dismissLabel = input('Ikke lige nu', ...(ngDevMode ? [{ debugName: "dismissLabel" }] : []));
28730
+ allow = output();
28731
+ dismiss = output();
28732
+ static EXIT_DURATION = 800;
28733
+ handleAllow() {
28734
+ if (this.dismissing())
28735
+ return;
28736
+ this.dismissing.set(true);
28737
+ setTimeout(() => this.allow.emit(), DsMobileNotificationPromptComponent.EXIT_DURATION);
28738
+ }
28739
+ handleDismiss() {
28740
+ if (this.dismissing())
28741
+ return;
28742
+ this.dismissing.set(true);
28743
+ setTimeout(() => this.dismiss.emit(), DsMobileNotificationPromptComponent.EXIT_DURATION);
28744
+ }
28745
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationPromptComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
28746
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DsMobileNotificationPromptComponent, isStandalone: true, selector: "ds-mobile-notification-prompt", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, allowLabel: { classPropertyName: "allowLabel", publicName: "allowLabel", isSignal: true, isRequired: false, transformFunction: null }, dismissLabel: { classPropertyName: "dismissLabel", publicName: "dismissLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { allow: "allow", dismiss: "dismiss" }, ngImport: i0, template: `
28747
+ <div class="notification-prompt" [class.is-dismissing]="dismissing()">
28748
+ <div
28749
+ class="notification-prompt__backdrop"
28750
+ [style.background]="whitelabel.headerSurface()">
28751
+ </div>
28752
+
28753
+ <!-- Illustration: floating icon tiles -->
28754
+ <div class="notification-prompt__illustration">
28755
+ <div class="icon-tiles">
28756
+
28757
+ <!-- Tile 1: service/heart-handshake (top-center, small 72px) -->
28758
+ <div class="tile-entry tile-entry--1">
28759
+ <div class="icon-tile icon-tile--1">
28760
+ <div class="icon-tile__icon">
28761
+ <svg viewBox="0 0 45 45" fill="none">
28762
+ <path d="M5.928 8.379c4.545-4.545 11.787-4.75 16.575-.615 4.785-4.135 12.026-3.93 16.571.615 4.537 4.537 4.749 11.76.637 16.549L25.153 39.536a3.75 3.75 0 0 1-5.098.19l-.206-.19L5.291 24.928c-4.112-4.789-3.9-12.012.637-16.549zm2.652 2.651c-3.2 3.201-3.292 8.334-.274 11.645l.274.288L22.5 36.884l9.943-9.945-6.628-6.628-1.988 1.989a4.688 4.688 0 0 1-6.628-6.628l3.94-3.943c-3.212-2.57-7.866-2.453-10.945.245l-.287.275zm15.91 5.303a1.875 1.875 0 0 1 2.651 0l7.954 7.954 1.327-1.325c3.295-3.295 3.295-8.637 0-11.932-3.2-3.201-8.333-3.292-11.645-.274l-.287.274-5.967 5.967a1.875 1.875 0 0 0-.146 2.497l.146.164a1.875 1.875 0 0 0 2.497.145l.164-.145 3.314-3.315z" fill="currentColor"/>
28763
+ </svg>
28764
+ </div>
28765
+ </div>
28766
+ </div>
28767
+
28768
+ <!-- Tile 2: calendar (right-bottom, medium 88px) -->
28769
+ <div class="tile-entry tile-entry--2">
28770
+ <div class="icon-tile icon-tile--medium icon-tile--2">
28771
+ <div class="icon-tile__icon">
28772
+ <svg viewBox="0 0 24 24" fill="none">
28773
+ <path d="M9 1v2h6V1h2v2h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h4V1h2zm11 9H4v9h16v-9zm-4.964 1.136l1.414 1.414-4.95 4.95-3.536-3.536L9.38 12.55l2.121 2.122 3.536-3.536zM7 5H4v3h16V5h-3v1h-2V5H9v1H7V5z" fill="currentColor"/>
28774
+ </svg>
28775
+ </div>
28776
+ </div>
28777
+ </div>
28778
+
28779
+ <!-- Tile 3: documents (bottom-left, small 72px) -->
28780
+ <div class="tile-entry tile-entry--3">
28781
+ <div class="icon-tile icon-tile--3">
28782
+ <div class="icon-tile__icon">
28783
+ <svg viewBox="0 0 24 24" fill="none">
28784
+ <path d="M20 22H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1zM19 4H5v16h14V4zM7 6h4v4H7V6zm0 6h10v2H7v-2zm0 4h10v2H7v-2zm6-9h4v2h-4V7z" fill="currentColor"/>
28785
+ </svg>
28786
+ </div>
28787
+ </div>
28788
+ </div>
28789
+
28790
+ <!-- Tile 4: messages (right-upper, medium 88px) -->
28791
+ <div class="tile-entry tile-entry--4">
28792
+ <div class="icon-tile icon-tile--medium icon-tile--4">
28793
+ <div class="icon-tile__icon">
28794
+ <svg viewBox="0 0 24 24" fill="none">
28795
+ <path d="M7.291 20.824L2 22l1.176-5.291A9.956 9.956 0 0 1 2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10a9.956 9.956 0 0 1-4.709-1.176zM7.581 18.71l.29.158A7.956 7.956 0 0 0 12 20c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8c0 1.458.39 2.823 1.131 4.13l.159.288-.69 3.1 3.1-.69z" fill="currentColor"/>
28796
+ </svg>
28797
+ </div>
28798
+ </div>
28799
+ </div>
28800
+
28801
+ <!-- Tile 5: home (left-middle, medium 88px) -->
28802
+ <div class="tile-entry tile-entry--5">
28803
+ <div class="icon-tile icon-tile--medium icon-tile--5">
28804
+ <div class="icon-tile__icon">
28805
+ <svg viewBox="0 0 24 24" fill="none">
28806
+ <path d="M19 21H5a1 1 0 0 1-1-1v-9H1l10.327-9.388a1 1 0 0 1 1.346 0L23 11h-3v9a1 1 0 0 1-1 1zM6 19h12v-9.157l-6-5.454-6 5.454V19zm2-4h8v2H8v-2z" fill="currentColor"/>
28807
+ </svg>
28808
+ </div>
28809
+ </div>
28810
+ </div>
28811
+
28812
+ <!-- Hero: bell icon (center, 120px) -->
28813
+ <div class="tile-entry tile-entry--hero">
28814
+ <div class="icon-tile icon-tile--hero">
28815
+ <div class="icon-tile__icon">
28816
+ <svg viewBox="0 0 24 24" fill="none">
28817
+ <path d="M20 17h2v2H2v-2h2v-7a8 8 0 1 1 16 0v7zm-2 0v-7a6 6 0 1 0-12 0v7h12zm-9 4h6v2H9v-2z" fill="currentColor"/>
28818
+ </svg>
28819
+ </div>
28820
+ <span class="icon-tile__badge">18</span>
28821
+ </div>
28822
+ </div>
28823
+
28824
+ </div>
28825
+ </div>
28826
+
28827
+ <!-- Heading + subtitle -->
28828
+ <div class="notification-prompt__texts">
28829
+ <h1 class="notification-prompt__heading">{{ heading() }}</h1>
28830
+ <p class="notification-prompt__subtitle">{{ subtitle() }}</p>
28831
+ </div>
28832
+
28833
+ <!-- CTA buttons -->
28834
+ <div class="notification-prompt__actions">
28835
+ <ds-button
28836
+ variant="primary"
28837
+ size="md"
28838
+ (clicked)="handleAllow()">
28839
+ {{ allowLabel() }}
28840
+ </ds-button>
28841
+ <ds-button
28842
+ class="dismiss-btn"
28843
+ variant="ghost"
28844
+ size="md"
28845
+ (clicked)="handleDismiss()">
28846
+ {{ dismissLabel() }}
28847
+ </ds-button>
28848
+ </div>
28849
+ </div>
28850
+ `, isInline: true, styles: ["@keyframes fadeSlideUp{0%{opacity:0;transform:translateY(32px)}to{opacity:1;transform:translateY(0)}}@keyframes tileEntry{0%{opacity:0;transform:translateY(40px) scale(.85)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes float1{0%{transform:var(--tile-transform) translate(0) rotate(0)}11%{transform:var(--tile-transform) translate(1.4px,-3px) rotate(.9deg)}23%{transform:var(--tile-transform) translate(2.2px,-6.5px) rotate(1.8deg)}37%{transform:var(--tile-transform) translate(.6px,-8.5px) rotate(.4deg)}50%{transform:var(--tile-transform) translate(-1.2px,-7px) rotate(-.8deg)}63%{transform:var(--tile-transform) translate(-1.8px,-4px) rotate(-1.6deg)}76%{transform:var(--tile-transform) translate(-.4px,-1.5px) rotate(-.5deg)}88%{transform:var(--tile-transform) translate(.8px,-.5px) rotate(.3deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float2{0%{transform:var(--tile-transform) translate(0) rotate(0)}12%{transform:var(--tile-transform) translate(-1px,-2.5px) rotate(-.7deg)}25%{transform:var(--tile-transform) translate(-2px,-5.5px) rotate(-1.5deg)}38%{transform:var(--tile-transform) translate(-.8px,-7px) rotate(-.3deg)}50%{transform:var(--tile-transform) translate(1px,-6px) rotate(1deg)}62%{transform:var(--tile-transform) translate(1.6px,-3.5px) rotate(1.6deg)}75%{transform:var(--tile-transform) translate(.5px,-1.5px) rotate(.6deg)}87%{transform:var(--tile-transform) translate(-.5px,-.3px) rotate(-.2deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float3{0%{transform:var(--tile-transform) translate(0) rotate(0)}10%{transform:var(--tile-transform) translate(.8px,-3px) rotate(1.2deg)}22%{transform:var(--tile-transform) translate(1.5px,-7px) rotate(2deg)}35%{transform:var(--tile-transform) translate(.3px,-10px) rotate(.8deg)}48%{transform:var(--tile-transform) translate(-1.5px,-9.5px) rotate(-.6deg)}60%{transform:var(--tile-transform) translate(-2px,-6px) rotate(-1.5deg)}72%{transform:var(--tile-transform) translate(-1px,-3px) rotate(-.8deg)}85%{transform:var(--tile-transform) translate(.3px,-1px) rotate(.3deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float4{0%{transform:var(--tile-transform) translate(0) rotate(0)}13%{transform:var(--tile-transform) translate(-.8px,-2px) rotate(-.8deg)}26%{transform:var(--tile-transform) translate(-1.5px,-5px) rotate(-1.6deg)}40%{transform:var(--tile-transform) translate(.2px,-7.5px) rotate(-.2deg)}52%{transform:var(--tile-transform) translate(1.8px,-6.5px) rotate(1.2deg)}65%{transform:var(--tile-transform) translate(1.2px,-4px) rotate(.8deg)}78%{transform:var(--tile-transform) translate(-.3px,-1.8px) rotate(-.4deg)}90%{transform:var(--tile-transform) translate(-.6px,-.4px) rotate(-.1deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float5{0%{transform:var(--tile-transform) translate(0) rotate(0)}9%{transform:var(--tile-transform) translate(1.2px,-2px) rotate(.6deg)}20%{transform:var(--tile-transform) translate(2px,-5px) rotate(1.5deg)}33%{transform:var(--tile-transform) translate(.8px,-8.5px) rotate(.5deg)}45%{transform:var(--tile-transform) translate(-1px,-9.5px) rotate(-1deg)}58%{transform:var(--tile-transform) translate(-1.8px,-7px) rotate(-1.8deg)}70%{transform:var(--tile-transform) translate(-.8px,-4px) rotate(-.8deg)}82%{transform:var(--tile-transform) translate(.4px,-1.5px) rotate(.2deg)}92%{transform:var(--tile-transform) translate(.6px,-.3px) rotate(.1deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes floatHero{0%{transform:translate(-50%) translate(0) rotate(0) scale(1)}10%{transform:translate(-50%) translate(.8px,-4px) rotate(.7deg) scale(1.006)}22%{transform:translate(-50%) translate(1.5px,-9px) rotate(1.4deg) scale(1.014)}35%{transform:translate(-50%) translate(.2px,-13px) rotate(.3deg) scale(1.02)}48%{transform:translate(-50%) translate(-1.5px,-12px) rotate(-1deg) scale(1.016)}60%{transform:translate(-50%) translate(-2px,-8px) rotate(-1.6deg) scale(1.01)}72%{transform:translate(-50%) translate(-.6px,-4px) rotate(-.5deg) scale(1.005)}85%{transform:translate(-50%) translate(.5px,-1.5px) rotate(.3deg) scale(1.002)}94%{transform:translate(-50%) translate(.3px,-.3px) rotate(.1deg) scale(1.001)}to{transform:translate(-50%) translate(0) rotate(0) scale(1)}}:host{display:flex;align-items:center;justify-content:center;position:fixed;inset:0;z-index:9999}.notification-prompt{width:100%;max-width:390px;height:100%;display:flex;flex-direction:column;gap:48px;overflow:clip;position:relative}.notification-prompt__backdrop{position:fixed;inset:0;z-index:-1}.notification-prompt__illustration{position:relative;flex:1 0 0;min-height:0;display:flex;align-items:center;justify-content:center;padding:0 24px}.icon-tiles{position:relative;width:100%;max-width:342px;height:100%}.tile-entry{position:absolute;opacity:0;animation:tileEntry .7s cubic-bezier(.22,1,.36,1) forwards;will-change:transform,opacity}.tile-entry--hero{inset:0;animation-delay:.2s;animation-duration:.8s}.tile-entry--1{inset:0;animation-delay:.35s}.tile-entry--4{inset:0;animation-delay:.3s}.tile-entry--5{inset:0;animation-delay:.4s}.tile-entry--3{inset:0;animation-delay:.45s}.tile-entry--2{inset:0;animation-delay:.5s}.icon-tile{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:20%;background:linear-gradient(180deg,#0000 95%,#00000026),linear-gradient(180deg,#fff6,#fff0 4.5%),linear-gradient(180deg,#ffffff26,#fff0),linear-gradient(180deg,#0000,#0000001a),var(--color-accent, #6B5FF5);will-change:transform}.icon-tile__icon{width:62.5%;height:62.5%;display:flex;align-items:center;justify-content:center}.icon-tile__icon svg{width:100%;height:100%;color:#fff;display:block}.icon-tile--1{--tile-transform: translate(-50%, 0) rotate(7.74deg);width:72px;height:72px;top:22%;left:50%;filter:blur(2px);animation:float1 6.8s linear infinite}.icon-tile--2{--tile-transform: rotate(9.27deg);width:88px;height:88px;bottom:6%;right:0;filter:blur(1px);animation:float2 7.4s linear infinite}.icon-tile--3{--tile-transform: rotate(-6.18deg);width:72px;height:72px;bottom:0;left:0;filter:blur(2px);animation:float3 8.6s linear infinite}.icon-tile--4{--tile-transform: rotate(20.57deg);width:88px;height:88px;top:36%;right:0;filter:blur(1px);animation:float4 7.1s linear infinite}.icon-tile--5{--tile-transform: rotate(-8.24deg);width:88px;height:88px;top:44%;left:0;filter:blur(1px);animation:float5 9.2s linear infinite}.icon-tile--hero{width:120px;height:120px;left:50%;bottom:14%;filter:none;border-radius:30px;z-index:2;animation:floatHero 6.2s linear infinite}.icon-tile__badge{position:absolute;top:12.5%;right:12%;min-width:26px;height:26px;padding:0 7px;border-radius:8px;background:var(--content-feedback-bold-red, #b91c1c);display:flex;align-items:center;justify-content:center;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:18px;line-height:1;letter-spacing:-.04em;color:#fff;white-space:nowrap;box-sizing:border-box}.notification-prompt__texts{position:relative;z-index:1;text-align:center;padding:0 16px;flex-shrink:0;opacity:0;animation:fadeSlideUp .6s .6s cubic-bezier(.22,1,.36,1) forwards}.notification-prompt__heading{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:32px;line-height:1.15;letter-spacing:-.02em;color:#fff;margin:0 0 4px}.notification-prompt__subtitle{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:400;font-size:18px;line-height:1.4;letter-spacing:-.02em;color:#eef0ff;margin:0}.notification-prompt__actions{position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;padding:0 24px 24px;padding-bottom:max(24px,env(safe-area-inset-bottom,24px));flex-shrink:0;opacity:0;animation:fadeSlideUp .6s .75s cubic-bezier(.22,1,.36,1) forwards}.notification-prompt__actions ds-button{display:block;width:100%}.notification-prompt__actions ds-button::ng-deep button{width:100%;border-radius:8px}.notification-prompt__actions ds-button.dismiss-btn::ng-deep button{color:#fff;background:transparent}@keyframes exitUp{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-48px)}}@keyframes exitFade{0%{opacity:1}to{opacity:0}}.is-dismissing .notification-prompt__illustration{animation:exitUp .5s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__texts{animation:exitUp .45s .15s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__actions{animation:exitUp .45s .3s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__backdrop{animation:exitFade .35s .45s cubic-bezier(.55,0,1,.45) forwards}@media (prefers-reduced-motion: reduce){.tile-entry,.icon-tile,.icon-tile--hero,.notification-prompt__texts,.notification-prompt__actions{animation:none!important;opacity:1!important}}\n"], dependencies: [{ kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }] });
28851
+ }
28852
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationPromptComponent, decorators: [{
28853
+ type: Component,
28854
+ args: [{ selector: 'ds-mobile-notification-prompt', standalone: true, imports: [DsButtonComponent], template: `
28855
+ <div class="notification-prompt" [class.is-dismissing]="dismissing()">
28856
+ <div
28857
+ class="notification-prompt__backdrop"
28858
+ [style.background]="whitelabel.headerSurface()">
28859
+ </div>
28860
+
28861
+ <!-- Illustration: floating icon tiles -->
28862
+ <div class="notification-prompt__illustration">
28863
+ <div class="icon-tiles">
28864
+
28865
+ <!-- Tile 1: service/heart-handshake (top-center, small 72px) -->
28866
+ <div class="tile-entry tile-entry--1">
28867
+ <div class="icon-tile icon-tile--1">
28868
+ <div class="icon-tile__icon">
28869
+ <svg viewBox="0 0 45 45" fill="none">
28870
+ <path d="M5.928 8.379c4.545-4.545 11.787-4.75 16.575-.615 4.785-4.135 12.026-3.93 16.571.615 4.537 4.537 4.749 11.76.637 16.549L25.153 39.536a3.75 3.75 0 0 1-5.098.19l-.206-.19L5.291 24.928c-4.112-4.789-3.9-12.012.637-16.549zm2.652 2.651c-3.2 3.201-3.292 8.334-.274 11.645l.274.288L22.5 36.884l9.943-9.945-6.628-6.628-1.988 1.989a4.688 4.688 0 0 1-6.628-6.628l3.94-3.943c-3.212-2.57-7.866-2.453-10.945.245l-.287.275zm15.91 5.303a1.875 1.875 0 0 1 2.651 0l7.954 7.954 1.327-1.325c3.295-3.295 3.295-8.637 0-11.932-3.2-3.201-8.333-3.292-11.645-.274l-.287.274-5.967 5.967a1.875 1.875 0 0 0-.146 2.497l.146.164a1.875 1.875 0 0 0 2.497.145l.164-.145 3.314-3.315z" fill="currentColor"/>
28871
+ </svg>
28872
+ </div>
28873
+ </div>
28874
+ </div>
28875
+
28876
+ <!-- Tile 2: calendar (right-bottom, medium 88px) -->
28877
+ <div class="tile-entry tile-entry--2">
28878
+ <div class="icon-tile icon-tile--medium icon-tile--2">
28879
+ <div class="icon-tile__icon">
28880
+ <svg viewBox="0 0 24 24" fill="none">
28881
+ <path d="M9 1v2h6V1h2v2h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h4V1h2zm11 9H4v9h16v-9zm-4.964 1.136l1.414 1.414-4.95 4.95-3.536-3.536L9.38 12.55l2.121 2.122 3.536-3.536zM7 5H4v3h16V5h-3v1h-2V5H9v1H7V5z" fill="currentColor"/>
28882
+ </svg>
28883
+ </div>
28884
+ </div>
28885
+ </div>
28886
+
28887
+ <!-- Tile 3: documents (bottom-left, small 72px) -->
28888
+ <div class="tile-entry tile-entry--3">
28889
+ <div class="icon-tile icon-tile--3">
28890
+ <div class="icon-tile__icon">
28891
+ <svg viewBox="0 0 24 24" fill="none">
28892
+ <path d="M20 22H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1zM19 4H5v16h14V4zM7 6h4v4H7V6zm0 6h10v2H7v-2zm0 4h10v2H7v-2zm6-9h4v2h-4V7z" fill="currentColor"/>
28893
+ </svg>
28894
+ </div>
28895
+ </div>
28896
+ </div>
28897
+
28898
+ <!-- Tile 4: messages (right-upper, medium 88px) -->
28899
+ <div class="tile-entry tile-entry--4">
28900
+ <div class="icon-tile icon-tile--medium icon-tile--4">
28901
+ <div class="icon-tile__icon">
28902
+ <svg viewBox="0 0 24 24" fill="none">
28903
+ <path d="M7.291 20.824L2 22l1.176-5.291A9.956 9.956 0 0 1 2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10a9.956 9.956 0 0 1-4.709-1.176zM7.581 18.71l.29.158A7.956 7.956 0 0 0 12 20c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8c0 1.458.39 2.823 1.131 4.13l.159.288-.69 3.1 3.1-.69z" fill="currentColor"/>
28904
+ </svg>
28905
+ </div>
28906
+ </div>
28907
+ </div>
28908
+
28909
+ <!-- Tile 5: home (left-middle, medium 88px) -->
28910
+ <div class="tile-entry tile-entry--5">
28911
+ <div class="icon-tile icon-tile--medium icon-tile--5">
28912
+ <div class="icon-tile__icon">
28913
+ <svg viewBox="0 0 24 24" fill="none">
28914
+ <path d="M19 21H5a1 1 0 0 1-1-1v-9H1l10.327-9.388a1 1 0 0 1 1.346 0L23 11h-3v9a1 1 0 0 1-1 1zM6 19h12v-9.157l-6-5.454-6 5.454V19zm2-4h8v2H8v-2z" fill="currentColor"/>
28915
+ </svg>
28916
+ </div>
28917
+ </div>
28918
+ </div>
28919
+
28920
+ <!-- Hero: bell icon (center, 120px) -->
28921
+ <div class="tile-entry tile-entry--hero">
28922
+ <div class="icon-tile icon-tile--hero">
28923
+ <div class="icon-tile__icon">
28924
+ <svg viewBox="0 0 24 24" fill="none">
28925
+ <path d="M20 17h2v2H2v-2h2v-7a8 8 0 1 1 16 0v7zm-2 0v-7a6 6 0 1 0-12 0v7h12zm-9 4h6v2H9v-2z" fill="currentColor"/>
28926
+ </svg>
28927
+ </div>
28928
+ <span class="icon-tile__badge">18</span>
28929
+ </div>
28930
+ </div>
28931
+
28932
+ </div>
28933
+ </div>
28934
+
28935
+ <!-- Heading + subtitle -->
28936
+ <div class="notification-prompt__texts">
28937
+ <h1 class="notification-prompt__heading">{{ heading() }}</h1>
28938
+ <p class="notification-prompt__subtitle">{{ subtitle() }}</p>
28939
+ </div>
28940
+
28941
+ <!-- CTA buttons -->
28942
+ <div class="notification-prompt__actions">
28943
+ <ds-button
28944
+ variant="primary"
28945
+ size="md"
28946
+ (clicked)="handleAllow()">
28947
+ {{ allowLabel() }}
28948
+ </ds-button>
28949
+ <ds-button
28950
+ class="dismiss-btn"
28951
+ variant="ghost"
28952
+ size="md"
28953
+ (clicked)="handleDismiss()">
28954
+ {{ dismissLabel() }}
28955
+ </ds-button>
28956
+ </div>
28957
+ </div>
28958
+ `, styles: ["@keyframes fadeSlideUp{0%{opacity:0;transform:translateY(32px)}to{opacity:1;transform:translateY(0)}}@keyframes tileEntry{0%{opacity:0;transform:translateY(40px) scale(.85)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes float1{0%{transform:var(--tile-transform) translate(0) rotate(0)}11%{transform:var(--tile-transform) translate(1.4px,-3px) rotate(.9deg)}23%{transform:var(--tile-transform) translate(2.2px,-6.5px) rotate(1.8deg)}37%{transform:var(--tile-transform) translate(.6px,-8.5px) rotate(.4deg)}50%{transform:var(--tile-transform) translate(-1.2px,-7px) rotate(-.8deg)}63%{transform:var(--tile-transform) translate(-1.8px,-4px) rotate(-1.6deg)}76%{transform:var(--tile-transform) translate(-.4px,-1.5px) rotate(-.5deg)}88%{transform:var(--tile-transform) translate(.8px,-.5px) rotate(.3deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float2{0%{transform:var(--tile-transform) translate(0) rotate(0)}12%{transform:var(--tile-transform) translate(-1px,-2.5px) rotate(-.7deg)}25%{transform:var(--tile-transform) translate(-2px,-5.5px) rotate(-1.5deg)}38%{transform:var(--tile-transform) translate(-.8px,-7px) rotate(-.3deg)}50%{transform:var(--tile-transform) translate(1px,-6px) rotate(1deg)}62%{transform:var(--tile-transform) translate(1.6px,-3.5px) rotate(1.6deg)}75%{transform:var(--tile-transform) translate(.5px,-1.5px) rotate(.6deg)}87%{transform:var(--tile-transform) translate(-.5px,-.3px) rotate(-.2deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float3{0%{transform:var(--tile-transform) translate(0) rotate(0)}10%{transform:var(--tile-transform) translate(.8px,-3px) rotate(1.2deg)}22%{transform:var(--tile-transform) translate(1.5px,-7px) rotate(2deg)}35%{transform:var(--tile-transform) translate(.3px,-10px) rotate(.8deg)}48%{transform:var(--tile-transform) translate(-1.5px,-9.5px) rotate(-.6deg)}60%{transform:var(--tile-transform) translate(-2px,-6px) rotate(-1.5deg)}72%{transform:var(--tile-transform) translate(-1px,-3px) rotate(-.8deg)}85%{transform:var(--tile-transform) translate(.3px,-1px) rotate(.3deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float4{0%{transform:var(--tile-transform) translate(0) rotate(0)}13%{transform:var(--tile-transform) translate(-.8px,-2px) rotate(-.8deg)}26%{transform:var(--tile-transform) translate(-1.5px,-5px) rotate(-1.6deg)}40%{transform:var(--tile-transform) translate(.2px,-7.5px) rotate(-.2deg)}52%{transform:var(--tile-transform) translate(1.8px,-6.5px) rotate(1.2deg)}65%{transform:var(--tile-transform) translate(1.2px,-4px) rotate(.8deg)}78%{transform:var(--tile-transform) translate(-.3px,-1.8px) rotate(-.4deg)}90%{transform:var(--tile-transform) translate(-.6px,-.4px) rotate(-.1deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float5{0%{transform:var(--tile-transform) translate(0) rotate(0)}9%{transform:var(--tile-transform) translate(1.2px,-2px) rotate(.6deg)}20%{transform:var(--tile-transform) translate(2px,-5px) rotate(1.5deg)}33%{transform:var(--tile-transform) translate(.8px,-8.5px) rotate(.5deg)}45%{transform:var(--tile-transform) translate(-1px,-9.5px) rotate(-1deg)}58%{transform:var(--tile-transform) translate(-1.8px,-7px) rotate(-1.8deg)}70%{transform:var(--tile-transform) translate(-.8px,-4px) rotate(-.8deg)}82%{transform:var(--tile-transform) translate(.4px,-1.5px) rotate(.2deg)}92%{transform:var(--tile-transform) translate(.6px,-.3px) rotate(.1deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes floatHero{0%{transform:translate(-50%) translate(0) rotate(0) scale(1)}10%{transform:translate(-50%) translate(.8px,-4px) rotate(.7deg) scale(1.006)}22%{transform:translate(-50%) translate(1.5px,-9px) rotate(1.4deg) scale(1.014)}35%{transform:translate(-50%) translate(.2px,-13px) rotate(.3deg) scale(1.02)}48%{transform:translate(-50%) translate(-1.5px,-12px) rotate(-1deg) scale(1.016)}60%{transform:translate(-50%) translate(-2px,-8px) rotate(-1.6deg) scale(1.01)}72%{transform:translate(-50%) translate(-.6px,-4px) rotate(-.5deg) scale(1.005)}85%{transform:translate(-50%) translate(.5px,-1.5px) rotate(.3deg) scale(1.002)}94%{transform:translate(-50%) translate(.3px,-.3px) rotate(.1deg) scale(1.001)}to{transform:translate(-50%) translate(0) rotate(0) scale(1)}}:host{display:flex;align-items:center;justify-content:center;position:fixed;inset:0;z-index:9999}.notification-prompt{width:100%;max-width:390px;height:100%;display:flex;flex-direction:column;gap:48px;overflow:clip;position:relative}.notification-prompt__backdrop{position:fixed;inset:0;z-index:-1}.notification-prompt__illustration{position:relative;flex:1 0 0;min-height:0;display:flex;align-items:center;justify-content:center;padding:0 24px}.icon-tiles{position:relative;width:100%;max-width:342px;height:100%}.tile-entry{position:absolute;opacity:0;animation:tileEntry .7s cubic-bezier(.22,1,.36,1) forwards;will-change:transform,opacity}.tile-entry--hero{inset:0;animation-delay:.2s;animation-duration:.8s}.tile-entry--1{inset:0;animation-delay:.35s}.tile-entry--4{inset:0;animation-delay:.3s}.tile-entry--5{inset:0;animation-delay:.4s}.tile-entry--3{inset:0;animation-delay:.45s}.tile-entry--2{inset:0;animation-delay:.5s}.icon-tile{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:20%;background:linear-gradient(180deg,#0000 95%,#00000026),linear-gradient(180deg,#fff6,#fff0 4.5%),linear-gradient(180deg,#ffffff26,#fff0),linear-gradient(180deg,#0000,#0000001a),var(--color-accent, #6B5FF5);will-change:transform}.icon-tile__icon{width:62.5%;height:62.5%;display:flex;align-items:center;justify-content:center}.icon-tile__icon svg{width:100%;height:100%;color:#fff;display:block}.icon-tile--1{--tile-transform: translate(-50%, 0) rotate(7.74deg);width:72px;height:72px;top:22%;left:50%;filter:blur(2px);animation:float1 6.8s linear infinite}.icon-tile--2{--tile-transform: rotate(9.27deg);width:88px;height:88px;bottom:6%;right:0;filter:blur(1px);animation:float2 7.4s linear infinite}.icon-tile--3{--tile-transform: rotate(-6.18deg);width:72px;height:72px;bottom:0;left:0;filter:blur(2px);animation:float3 8.6s linear infinite}.icon-tile--4{--tile-transform: rotate(20.57deg);width:88px;height:88px;top:36%;right:0;filter:blur(1px);animation:float4 7.1s linear infinite}.icon-tile--5{--tile-transform: rotate(-8.24deg);width:88px;height:88px;top:44%;left:0;filter:blur(1px);animation:float5 9.2s linear infinite}.icon-tile--hero{width:120px;height:120px;left:50%;bottom:14%;filter:none;border-radius:30px;z-index:2;animation:floatHero 6.2s linear infinite}.icon-tile__badge{position:absolute;top:12.5%;right:12%;min-width:26px;height:26px;padding:0 7px;border-radius:8px;background:var(--content-feedback-bold-red, #b91c1c);display:flex;align-items:center;justify-content:center;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:18px;line-height:1;letter-spacing:-.04em;color:#fff;white-space:nowrap;box-sizing:border-box}.notification-prompt__texts{position:relative;z-index:1;text-align:center;padding:0 16px;flex-shrink:0;opacity:0;animation:fadeSlideUp .6s .6s cubic-bezier(.22,1,.36,1) forwards}.notification-prompt__heading{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:32px;line-height:1.15;letter-spacing:-.02em;color:#fff;margin:0 0 4px}.notification-prompt__subtitle{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:400;font-size:18px;line-height:1.4;letter-spacing:-.02em;color:#eef0ff;margin:0}.notification-prompt__actions{position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;padding:0 24px 24px;padding-bottom:max(24px,env(safe-area-inset-bottom,24px));flex-shrink:0;opacity:0;animation:fadeSlideUp .6s .75s cubic-bezier(.22,1,.36,1) forwards}.notification-prompt__actions ds-button{display:block;width:100%}.notification-prompt__actions ds-button::ng-deep button{width:100%;border-radius:8px}.notification-prompt__actions ds-button.dismiss-btn::ng-deep button{color:#fff;background:transparent}@keyframes exitUp{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-48px)}}@keyframes exitFade{0%{opacity:1}to{opacity:0}}.is-dismissing .notification-prompt__illustration{animation:exitUp .5s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__texts{animation:exitUp .45s .15s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__actions{animation:exitUp .45s .3s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__backdrop{animation:exitFade .35s .45s cubic-bezier(.55,0,1,.45) forwards}@media (prefers-reduced-motion: reduce){.tile-entry,.icon-tile,.icon-tile--hero,.notification-prompt__texts,.notification-prompt__actions{animation:none!important;opacity:1!important}}\n"] }]
28959
+ }], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], allowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowLabel", required: false }] }], dismissLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissLabel", required: false }] }], allow: [{ type: i0.Output, args: ["allow"] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
28960
+
28961
+ const NOTIFICATION_ICON_MAP = {
28962
+ inquiry_update: 'remixFileList3Line',
28963
+ inquiry_assigned: 'remixFileList3Line',
28964
+ inquiry_resolved: 'remixFileList3Line',
28965
+ community_post: 'remixCommunityLine',
28966
+ community_comment: 'remixCommunityLine',
28967
+ community_like: 'remixCommunityLine',
28968
+ community_mention: 'remixCommunityLine',
28969
+ booking_confirmed: 'remixCalendarCheckLine',
28970
+ booking_cancelled: 'remixCalendarCheckLine',
28971
+ booking_reminder: 'remixCalendarCheckLine',
28972
+ facility_available: 'remixCalendarCheckLine',
28973
+ message_new: 'remixMessage3Line',
28974
+ message_group: 'remixMessage3Line',
28975
+ service_update: 'remixServiceLine',
28976
+ handbook_update: 'remixBook2Line',
28977
+ system_announcement: 'remixHomeSmile2Line',
28978
+ system_welcome: 'remixHomeSmile2Line',
28979
+ invite_received: 'remixHomeSmile2Line',
28980
+ family_access: 'remixGroupLine',
28981
+ };
28982
+
28983
+ function minutesAgo(m) { return new Date(Date.now() - m * 60_000); }
28984
+ function hoursAgo(h) { return new Date(Date.now() - h * 3_600_000); }
28985
+ function daysAgo(d, hour = 12, minute = 0) {
28986
+ const date = new Date();
28987
+ date.setDate(date.getDate() - d);
28988
+ date.setHours(hour, minute, 0, 0);
28989
+ return date;
28990
+ }
28991
+ const SAMPLE_NOTIFICATIONS = [
28992
+ // Today — inquiry #2 "Problem med vandtryk"
28993
+ { id: '1', type: 'inquiry_update', leading: 'icon', targetId: '2', title: 'Problem med vandtryk', message: 'Din henvendelse er blevet opdateret af ejendomskontoret.', createdAt: minutesAgo(5), read: false },
28994
+ // Today — Mette Larsen commented on post-1
28995
+ { id: '2', type: 'community_comment', leading: 'avatar', targetId: 'post-1', avatarInitials: 'ML', title: 'Mette Larsen', message: 'Kommenterede: "Velkommen til fællesskabet!"', createdAt: minutesAgo(23), read: false },
28996
+ // Today — Marcus Lindqvist sent message in conv-2
28997
+ { id: '3', type: 'message_new', leading: 'avatar', targetId: 'conv-2', avatarInitials: 'ML', title: 'Marcus Lindqvist', message: 'Kan du tjekke støjproblemet igen?', createdAt: minutesAgo(10), read: false },
28998
+ // Today — booking-1 "Festlokale på taget" confirmed
28999
+ { id: '4', type: 'booking_confirmed', leading: 'image', targetId: 'booking-1', imageSrc: '/Assets/Dummy-photos/rooftop-party.jpg', title: 'Festlokale på taget', message: 'Din booking d. 14. feb kl. 9:00–17:00 er bekræftet.', createdAt: hoursAgo(2), read: true },
29000
+ // Today — Anders Jensen commented on post-2
29001
+ { id: '5', type: 'community_comment', leading: 'avatar', targetId: 'post-2', avatarInitials: 'AJ', title: 'Anders Jensen', message: 'Kommenterede: "Wow, den udsigt er fantastisk!"', createdAt: hoursAgo(3), read: true },
29002
+ // Yesterday — Blik Partner A/S assigned to inquiry #2
29003
+ { id: '6', type: 'inquiry_assigned', leading: 'avatar', targetId: '2', avatarInitials: 'BP', title: 'Blik Partner A/S', message: 'Er blevet tildelt din henvendelse om VVS.', createdAt: daysAgo(1, 16, 45), read: true },
29004
+ // Yesterday — Sophie Andersen posted in community (post-2)
29005
+ { id: '7', type: 'community_post', leading: 'avatar', targetId: 'post-2', avatarInitials: 'SA', title: 'Sophie Andersen', message: 'Nyt opslag med billeder fra ejendommen.', createdAt: daysAgo(1, 14, 20), read: true },
29006
+ // Yesterday — booking-3 "Boremaskinen" reminder
29007
+ { id: '8', type: 'booking_reminder', leading: 'image', targetId: 'booking-3', imageSrc: '/Assets/Dummy-photos/handyman.jpg', title: 'Boremaskinen', message: 'Du har en booking d. 25. mar kl. 10:00–14:00.', createdAt: daysAgo(1, 9, 0), read: true },
29008
+ // Yesterday — facility-1 "Boremaskinen" available
29009
+ { id: '9', type: 'facility_available', leading: 'image', targetId: 'facility-1', imageSrc: '/Assets/Dummy-photos/handyman.jpg', title: 'Boremaskinen', message: 'Boremaskinen er nu ledig til booking.', createdAt: daysAgo(1, 8, 15), read: true },
29010
+ // Yesterday — group "Vaskeri & møde" message
29011
+ { id: '10', type: 'message_group', leading: 'avatar', targetId: 'conv-group-demo', avatarInitials: 'VM', title: 'Vaskeri & møde', message: 'Anna Berg: "Vi mødes kl. 19 i fællesrummet."', createdAt: daysAgo(1, 19, 30), read: true },
29012
+ // Earlier — inquiry #3 resolved
29013
+ { id: '11', type: 'inquiry_resolved', leading: 'icon', targetId: '3', title: 'Varme virker ikke ordentligt', message: 'Din henvendelse er nu markeret som løst.', createdAt: daysAgo(3), read: true },
29014
+ // Earlier — handbook updated
29015
+ { id: '12', type: 'handbook_update', leading: 'logo', title: 'Håndbog opdateret', message: 'Afsnittet "Forsyninger" er blevet opdateret med ny information.', createdAt: daysAgo(4), read: true },
29016
+ // Earlier — ElektroTek ApS service update
29017
+ { id: '13', type: 'service_update', leading: 'image', targetId: 'v-4', imageSrc: '/Assets/dummy-logos/electrician-logo.svg', title: 'ElektroTek ApS', message: 'El-service er nu tilgængelig via Services.', createdAt: daysAgo(5), read: true },
29018
+ // Earlier — Thomas Hansen commented on post-2
29019
+ { id: '14', type: 'community_comment', leading: 'avatar', targetId: 'post-2', avatarInitials: 'TH', title: 'Thomas Hansen', message: 'Kommenterede: "Smuk! Jeg kan også se byens silhuet fra min lejlighed 🌆"', createdAt: daysAgo(6), read: true },
29020
+ // Earlier — booking-2 "Gæsteparkering" cancelled
29021
+ { id: '15', type: 'booking_cancelled', leading: 'image', targetId: 'booking-2', imageSrc: '/Assets/Dummy-photos/parking.jpg', title: 'Gæsteparkering', message: 'Din booking d. 20. mar kl. 8:00–20:00 er blevet annulleret.', createdAt: daysAgo(7), read: true },
29022
+ // Earlier — system announcement
29023
+ { id: '16', type: 'system_announcement', leading: 'logo', title: 'Systemmeddelelse', message: 'Planlagt vedligeholdelse d. 1. maj kl. 02:00–04:00.', createdAt: daysAgo(8), read: true },
29024
+ // Earlier — Karl Johansson message in conv-4
29025
+ { id: '17', type: 'message_new', leading: 'avatar', targetId: 'conv-4', avatarInitials: 'KJ', title: 'Karl Johansson', message: 'Hej! Er vaskerummet ledigt i morgen kl. 10?', createdAt: daysAgo(9), read: true },
29026
+ // Earlier — Sara Lindqvist family access
29027
+ { id: '18', type: 'family_access', leading: 'avatar', title: 'Sara Lindqvist', avatarInitials: 'SL', message: 'Sara Lindqvist har fået adgang til din konto.', createdAt: daysAgo(10), read: true },
29028
+ // Earlier — system welcome
29029
+ { id: '19', type: 'system_welcome', leading: 'logo', title: 'Velkommen!', message: 'Velkommen til Propbinder – vi glæder os til at hjælpe dig.', createdAt: daysAgo(11), read: true },
29030
+ ];
29031
+
29032
+ const TYPE_TO_CATEGORY = {
29033
+ inquiry_update: 'inquiries',
29034
+ inquiry_assigned: 'inquiries',
29035
+ inquiry_resolved: 'inquiries',
29036
+ community_post: 'community',
29037
+ community_comment: 'community',
29038
+ community_like: 'community',
29039
+ community_mention: 'community',
29040
+ booking_confirmed: 'bookings',
29041
+ booking_cancelled: 'bookings',
29042
+ booking_reminder: 'bookings',
29043
+ facility_available: 'bookings',
29044
+ message_new: 'messages',
29045
+ message_group: 'messages',
29046
+ service_update: 'services',
29047
+ handbook_update: 'handbook',
29048
+ system_announcement: 'system',
29049
+ system_welcome: 'system',
29050
+ invite_received: 'system',
29051
+ family_access: 'system',
29052
+ };
29053
+ class NotificationService {
29054
+ notifications = signal(SAMPLE_NOTIFICATIONS, ...(ngDevMode ? [{ debugName: "notifications" }] : []));
29055
+ unreadCount = computed(() => this.notifications().filter(n => !n.read).length, ...(ngDevMode ? [{ debugName: "unreadCount" }] : []));
29056
+ /**
29057
+ * Push notification delivery preferences per category.
29058
+ * Controls which types of push notifications the device receives —
29059
+ * does NOT filter the in-app notification list.
29060
+ */
29061
+ disabledPushCategories = signal(new Set(), ...(ngDevMode ? [{ debugName: "disabledPushCategories" }] : []));
29062
+ isPushEnabled(category) {
29063
+ return !this.disabledPushCategories().has(category);
29064
+ }
29065
+ setPushEnabled(category, enabled) {
29066
+ this.disabledPushCategories.update(set => {
29067
+ const next = new Set(set);
29068
+ if (enabled) {
29069
+ next.delete(category);
29070
+ }
29071
+ else {
29072
+ next.add(category);
29073
+ }
29074
+ return next;
29075
+ });
29076
+ }
29077
+ /**
29078
+ * Add a new notification to the top of the list.
29079
+ * Intended for downstream push integration — call this when a
29080
+ * real push payload arrives via Capacitor's `pushNotificationReceived` listener.
29081
+ */
29082
+ addNotification(item) {
29083
+ this.notifications.update(list => [item, ...list]);
29084
+ }
29085
+ markAsRead(id) {
29086
+ this.notifications.update(list => list.map(n => n.id === id ? { ...n, read: true } : n));
29087
+ }
29088
+ markAllAsRead() {
29089
+ this.notifications.update(list => list.map(n => ({ ...n, read: true })));
29090
+ }
29091
+ /**
29092
+ * Remove a single notification from the list permanently.
29093
+ */
29094
+ removeNotification(id) {
29095
+ this.notifications.update(list => list.filter(n => n.id !== id));
29096
+ }
29097
+ /**
29098
+ * Remove all notifications from the list permanently.
29099
+ */
29100
+ clearAll() {
29101
+ this.notifications.set([]);
29102
+ }
29103
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
29104
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, providedIn: 'root' });
29105
+ }
29106
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, decorators: [{
29107
+ type: Injectable,
29108
+ args: [{ providedIn: 'root' }]
29109
+ }] });
29110
+
29111
+ class RelativeTimePipe {
29112
+ transform(date) {
29113
+ if (!date)
29114
+ return '';
29115
+ const now = Date.now();
29116
+ const diffMs = now - date.getTime();
29117
+ const diffSec = Math.floor(diffMs / 1000);
29118
+ const diffMin = Math.floor(diffSec / 60);
29119
+ const diffHr = Math.floor(diffMin / 60);
29120
+ const diffDays = Math.floor(diffHr / 24);
29121
+ if (diffSec < 60)
29122
+ return 'Nu';
29123
+ if (diffMin < 60)
29124
+ return `${diffMin} min siden`;
29125
+ if (diffHr < 24)
29126
+ return `${diffHr} ${diffHr === 1 ? 'time' : 'timer'} siden`;
29127
+ const today = new Date();
29128
+ today.setHours(0, 0, 0, 0);
29129
+ const yesterday = new Date(today.getTime() - 86_400_000);
29130
+ if (date.getTime() >= yesterday.getTime() && date.getTime() < today.getTime()) {
29131
+ return `I går, ${pad(date.getHours())}:${pad(date.getMinutes())}`;
29132
+ }
29133
+ if (diffDays < 7)
29134
+ return `${diffDays} dage siden`;
29135
+ const day = date.getDate();
29136
+ const monthNames = ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'];
29137
+ return `${day}. ${monthNames[date.getMonth()]}`;
29138
+ }
29139
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RelativeTimePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
29140
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: RelativeTimePipe, isStandalone: true, name: "relativeTime" });
29141
+ }
29142
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RelativeTimePipe, decorators: [{
29143
+ type: Pipe,
29144
+ args: [{ name: 'relativeTime', standalone: true, pure: true }]
29145
+ }] });
29146
+ function pad(n) {
29147
+ return n < 10 ? `0${n}` : `${n}`;
29148
+ }
29149
+ /**
29150
+ * Bucket a date into 'today' | 'yesterday' | 'earlier' for grouping notifications.
29151
+ */
29152
+ function dateBucket(date) {
29153
+ const now = new Date();
29154
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
29155
+ const yesterday = new Date(today.getTime() - 86_400_000);
29156
+ if (date.getTime() >= today.getTime())
29157
+ return 'today';
29158
+ if (date.getTime() >= yesterday.getTime())
29159
+ return 'yesterday';
29160
+ return 'earlier';
29161
+ }
29162
+
29163
+ class DsMobileNotificationModalComponent {
29164
+ modalController = inject(ModalController);
29165
+ notificationService = inject(NotificationService);
29166
+ bottomSheet = inject(DsMobileBottomSheetService);
29167
+ groups = computed(() => {
29168
+ const items = this.notificationService.notifications();
29169
+ if (!items || items.length === 0)
29170
+ return [];
29171
+ const todayItems = [];
29172
+ const yesterdayItems = [];
29173
+ const earlierItems = [];
29174
+ for (const item of items) {
29175
+ const bucket = dateBucket(item.createdAt);
29176
+ if (bucket === 'today')
29177
+ todayItems.push(item);
29178
+ else if (bucket === 'yesterday')
29179
+ yesterdayItems.push(item);
29180
+ else
29181
+ earlierItems.push(item);
29182
+ }
29183
+ const groups = [];
29184
+ if (todayItems.length > 0)
29185
+ groups.push({ label: 'I dag', items: todayItems });
29186
+ if (yesterdayItems.length > 0)
29187
+ groups.push({ label: 'I går', items: yesterdayItems });
29188
+ if (earlierItems.length > 0)
29189
+ groups.push({ label: 'Tidligere', items: earlierItems });
29190
+ return groups;
29191
+ }, ...(ngDevMode ? [{ debugName: "groups" }] : []));
29192
+ iconFor(type) {
29193
+ return NOTIFICATION_ICON_MAP[type] ?? 'remixNotificationLine';
29194
+ }
29195
+ TYPE_LABELS = {
29196
+ inquiry_update: 'Henvendelse',
29197
+ inquiry_assigned: 'Henvendelse',
29198
+ inquiry_resolved: 'Henvendelse',
29199
+ community_post: 'Fællesskab',
29200
+ community_comment: 'Kommentar',
29201
+ community_like: 'Fællesskab',
29202
+ community_mention: 'Omtale',
29203
+ booking_confirmed: 'Booking',
29204
+ booking_cancelled: 'Booking',
29205
+ booking_reminder: 'Booking',
29206
+ facility_available: 'Facilitet',
29207
+ message_new: 'Besked',
29208
+ message_group: 'Gruppebesked',
29209
+ service_update: 'Service',
29210
+ handbook_update: 'Håndbog',
29211
+ system_announcement: 'System',
29212
+ system_welcome: 'System',
29213
+ invite_received: 'Invitation',
29214
+ family_access: 'Familie',
29215
+ };
29216
+ labelFor(type) {
29217
+ return this.TYPE_LABELS[type] ?? '';
29218
+ }
29219
+ handleNotificationClick(item) {
29220
+ this.modalController.dismiss(item);
29221
+ }
29222
+ async handleLongPress(item) {
29223
+ const actions = [
29224
+ { action: 'mark_read', title: item.read ? 'Marker som ulæst' : 'Marker som læst', icon: item.read ? 'remixMailUnreadLine' : 'remixMailOpenLine', destructive: false },
29225
+ { action: 'remove', title: 'Fjern notifikation', icon: 'remixDeleteBinLine', destructive: true },
29226
+ ];
29227
+ const sheet = await this.bottomSheet.create({
29228
+ component: DsMobileActionsBottomSheetComponent,
29229
+ componentProps: { customActionGroups: [{ actions }] },
29230
+ breakpoints: [0, 1],
29231
+ initialBreakpoint: 1,
29232
+ handle: true,
29233
+ backdropDismiss: true,
29234
+ cssClass: 'auto-height',
29235
+ });
29236
+ const result = await sheet.onWillDismiss();
29237
+ if (result.role === 'select' && result.data) {
29238
+ const action = result.data.action;
29239
+ if (action === 'mark_read') {
29240
+ if (item.read) {
29241
+ this.notificationService.notifications.update(list => list.map(n => n.id === item.id ? { ...n, read: false } : n));
29242
+ }
29243
+ else {
29244
+ this.notificationService.markAsRead(item.id);
29245
+ }
29246
+ }
29247
+ else if (action === 'remove') {
29248
+ this.notificationService.removeNotification(item.id);
29249
+ }
29250
+ }
29251
+ }
29252
+ async openBulkActions() {
29253
+ const actions = [
29254
+ { action: 'mark_all_read', title: 'Marker alle som læst', icon: 'remixMailOpenLine', destructive: false },
29255
+ { action: 'clear_all', title: 'Ryd alle notifikationer', icon: 'remixDeleteBinLine', destructive: true },
29256
+ ];
29257
+ const sheet = await this.bottomSheet.create({
29258
+ component: DsMobileActionsBottomSheetComponent,
29259
+ componentProps: { customActionGroups: [{ actions }] },
29260
+ breakpoints: [0, 1],
29261
+ initialBreakpoint: 1,
29262
+ handle: true,
29263
+ backdropDismiss: true,
29264
+ cssClass: 'auto-height',
29265
+ });
29266
+ const result = await sheet.onWillDismiss();
29267
+ if (result.role === 'select' && result.data) {
29268
+ const action = result.data.action;
29269
+ if (action === 'mark_all_read') {
29270
+ this.notificationService.markAllAsRead();
29271
+ }
29272
+ else if (action === 'clear_all') {
29273
+ this.notificationService.clearAll();
29274
+ }
29275
+ }
29276
+ }
29277
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
29278
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileNotificationModalComponent, isStandalone: true, selector: "ds-mobile-notification-modal", ngImport: i0, template: `
29279
+ <ds-mobile-modal-base
29280
+ headerTitle="Notifikationer"
29281
+ [showHeader]="true"
29282
+ closeButtonLabel="Luk">
29283
+
29284
+ <ds-icon-button
29285
+ header-trailing
29286
+ class="more-button"
29287
+ icon="remixMoreFill"
29288
+ variant="secondary"
29289
+ size="lg"
29290
+ (clicked)="openBulkActions()"
29291
+ aria-label="Flere handlinger"
29292
+ />
29293
+
29294
+ @if (groups().length > 0) {
29295
+ @for (group of groups(); track group.label) {
29296
+ <div class="notification-group">
29297
+ <div class="notification-group__label">{{ group.label }}</div>
29298
+
29299
+ @for (item of group.items; track item.id) {
29300
+ <ds-mobile-list-item
29301
+ [interactive]="true"
29302
+ [showDivider]="!$last"
29303
+ [enableLongPress]="true"
29304
+ [showDesktopMoreButton]="false"
29305
+ align="top"
29306
+ (itemClick)="handleNotificationClick(item)"
29307
+ (longPress)="handleLongPress(item)">
29308
+
29309
+ @if (item.leading === 'icon') {
29310
+ <ds-avatar
29311
+ content-leading
29312
+ type="icon"
29313
+ [iconName]="item.iconName || iconFor(item.type)"
29314
+ size="md"
29315
+ />
29316
+ }
29317
+
29318
+ @if (item.leading === 'avatar') {
29319
+ <ds-avatar
29320
+ content-leading
29321
+ [type]="item.avatarSrc ? 'photo' : 'initials'"
29322
+ [initials]="item.avatarInitials || ''"
29323
+ [src]="item.avatarSrc || ''"
29324
+ size="md"
29325
+ />
29326
+ }
29327
+
29328
+ @if (item.leading === 'image') {
29329
+ <div content-leading class="notification-image">
29330
+ <img [src]="item.imageSrc" [alt]="item.title" />
29331
+ </div>
29332
+ }
29333
+
29334
+ @if (item.leading === 'logo') {
29335
+ <div content-leading class="notification-logo">
29336
+ <ds-app-icon size="lg" />
29337
+ </div>
29338
+ }
29339
+
29340
+ <div content-main class="notification-content">
29341
+ <div class="notification-header">
29342
+ <div class="notification-header__details">
29343
+ <span class="notification-header__name" [class.notification-header__name--unread]="!item.read">
29344
+ {{ item.title }}
29345
+ </span>
29346
+ <span class="notification-header__meta">
29347
+ {{ labelFor(item.type) }}
29348
+ <span class="notification-header__separator">&middot;</span>
29349
+ {{ item.createdAt | relativeTime }}
29350
+ </span>
29351
+ </div>
29352
+ </div>
29353
+ <p class="notification-message">{{ item.message }}</p>
29354
+ </div>
29355
+
29356
+ @if (!item.read) {
29357
+ <div content-trailing class="notification-unread-dot"></div>
29358
+ }
29359
+ </ds-mobile-list-item>
29360
+ }
29361
+ </div>
29362
+ }
29363
+ } @else {
29364
+ <ds-mobile-empty-state
29365
+ imageSrc="/Assets/empty-state-notification.svg"
29366
+ imageAlt="Ingen notifikationer"
29367
+ title="Alt er stille herinde"
29368
+ description="Når der sker noget nyt, finder du det her."
29369
+ />
29370
+ }
29371
+ </ds-mobile-modal-base>
29372
+ `, isInline: true, styles: [":host ::ng-deep .header-content{gap:8px!important}.more-button{flex-shrink:0;border-radius:50%}.more-button::ng-deep button{border-radius:50%!important;width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;padding:0!important;display:flex!important;align-items:center!important;justify-content:center!important}.notification-group{padding:0 20px}.notification-group+.notification-group{margin-top:8px;border-top:1px solid var(--color-border-default-secondary, #E5E7EB)}.notification-group__label{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:13px;line-height:1;letter-spacing:-.02em;color:var(--text-color-default-tertiary, #999);text-transform:none;padding:16px 0 4px}.notification-content{display:flex;flex-direction:column;gap:2px;min-width:0}.notification-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}.notification-header__details{display:flex;flex-direction:column;align-items:flex-start;gap:2px;flex:1;min-width:0}.notification-header__name{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:600;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.notification-header__name--unread{font-weight:600}.notification-header__meta{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:4px}.notification-header__separator{color:var(--text-color-default-tertiary, #737373);opacity:.5}.notification-message{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.notification-image{width:32px;height:32px;border-radius:8px;overflow:hidden;flex-shrink:0}.notification-image img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover;display:block}.notification-logo{flex-shrink:0}.notification-unread-dot{width:8px;height:8px;border-radius:50%;background:var(--color-accent, #6B5FF5);flex-shrink:0;margin-top:6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsMobileModalBaseComponent, selector: "ds-mobile-modal-base", inputs: ["headerTitleInteractive", "showHeader"], outputs: ["titleClick"] }, { kind: "component", type: DsMobileListItemComponent, selector: "ds-mobile-list-item", inputs: ["leadingSize", "variant", "align", "flushTop", "interactive", "disabled", "loading", "enableLongPress", "showDesktopMoreButton", "moreActions", "moreButtonAriaLabel", "interactiveOffset", "title", "subtitle", "showDivider", "dividerSpacing"], outputs: ["itemClick", "moreButtonClick"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsAppIconComponent, selector: "ds-app-icon", inputs: ["size"] }, { kind: "component", type: DsMobileEmptyStateComponent, selector: "ds-mobile-empty-state", inputs: ["imageSrc", "imageAlt", "title", "description"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "pipe", type: RelativeTimePipe, name: "relativeTime" }] });
29373
+ }
29374
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalComponent, decorators: [{
29375
+ type: Component,
29376
+ args: [{ selector: 'ds-mobile-notification-modal', standalone: true, imports: [CommonModule, DsMobileModalBaseComponent, DsMobileListItemComponent, DsAvatarComponent, DsAppIconComponent, DsMobileEmptyStateComponent, DsIconButtonComponent, RelativeTimePipe], template: `
29377
+ <ds-mobile-modal-base
29378
+ headerTitle="Notifikationer"
29379
+ [showHeader]="true"
29380
+ closeButtonLabel="Luk">
29381
+
29382
+ <ds-icon-button
29383
+ header-trailing
29384
+ class="more-button"
29385
+ icon="remixMoreFill"
29386
+ variant="secondary"
29387
+ size="lg"
29388
+ (clicked)="openBulkActions()"
29389
+ aria-label="Flere handlinger"
29390
+ />
29391
+
29392
+ @if (groups().length > 0) {
29393
+ @for (group of groups(); track group.label) {
29394
+ <div class="notification-group">
29395
+ <div class="notification-group__label">{{ group.label }}</div>
29396
+
29397
+ @for (item of group.items; track item.id) {
29398
+ <ds-mobile-list-item
29399
+ [interactive]="true"
29400
+ [showDivider]="!$last"
29401
+ [enableLongPress]="true"
29402
+ [showDesktopMoreButton]="false"
29403
+ align="top"
29404
+ (itemClick)="handleNotificationClick(item)"
29405
+ (longPress)="handleLongPress(item)">
29406
+
29407
+ @if (item.leading === 'icon') {
29408
+ <ds-avatar
29409
+ content-leading
29410
+ type="icon"
29411
+ [iconName]="item.iconName || iconFor(item.type)"
29412
+ size="md"
29413
+ />
29414
+ }
29415
+
29416
+ @if (item.leading === 'avatar') {
29417
+ <ds-avatar
29418
+ content-leading
29419
+ [type]="item.avatarSrc ? 'photo' : 'initials'"
29420
+ [initials]="item.avatarInitials || ''"
29421
+ [src]="item.avatarSrc || ''"
29422
+ size="md"
29423
+ />
29424
+ }
29425
+
29426
+ @if (item.leading === 'image') {
29427
+ <div content-leading class="notification-image">
29428
+ <img [src]="item.imageSrc" [alt]="item.title" />
29429
+ </div>
29430
+ }
29431
+
29432
+ @if (item.leading === 'logo') {
29433
+ <div content-leading class="notification-logo">
29434
+ <ds-app-icon size="lg" />
29435
+ </div>
29436
+ }
29437
+
29438
+ <div content-main class="notification-content">
29439
+ <div class="notification-header">
29440
+ <div class="notification-header__details">
29441
+ <span class="notification-header__name" [class.notification-header__name--unread]="!item.read">
29442
+ {{ item.title }}
29443
+ </span>
29444
+ <span class="notification-header__meta">
29445
+ {{ labelFor(item.type) }}
29446
+ <span class="notification-header__separator">&middot;</span>
29447
+ {{ item.createdAt | relativeTime }}
29448
+ </span>
29449
+ </div>
29450
+ </div>
29451
+ <p class="notification-message">{{ item.message }}</p>
29452
+ </div>
29453
+
29454
+ @if (!item.read) {
29455
+ <div content-trailing class="notification-unread-dot"></div>
29456
+ }
29457
+ </ds-mobile-list-item>
29458
+ }
29459
+ </div>
29460
+ }
29461
+ } @else {
29462
+ <ds-mobile-empty-state
29463
+ imageSrc="/Assets/empty-state-notification.svg"
29464
+ imageAlt="Ingen notifikationer"
29465
+ title="Alt er stille herinde"
29466
+ description="Når der sker noget nyt, finder du det her."
29467
+ />
29468
+ }
29469
+ </ds-mobile-modal-base>
29470
+ `, styles: [":host ::ng-deep .header-content{gap:8px!important}.more-button{flex-shrink:0;border-radius:50%}.more-button::ng-deep button{border-radius:50%!important;width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;padding:0!important;display:flex!important;align-items:center!important;justify-content:center!important}.notification-group{padding:0 20px}.notification-group+.notification-group{margin-top:8px;border-top:1px solid var(--color-border-default-secondary, #E5E7EB)}.notification-group__label{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:13px;line-height:1;letter-spacing:-.02em;color:var(--text-color-default-tertiary, #999);text-transform:none;padding:16px 0 4px}.notification-content{display:flex;flex-direction:column;gap:2px;min-width:0}.notification-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}.notification-header__details{display:flex;flex-direction:column;align-items:flex-start;gap:2px;flex:1;min-width:0}.notification-header__name{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:600;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.notification-header__name--unread{font-weight:600}.notification-header__meta{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:4px}.notification-header__separator{color:var(--text-color-default-tertiary, #737373);opacity:.5}.notification-message{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.notification-image{width:32px;height:32px;border-radius:8px;overflow:hidden;flex-shrink:0}.notification-image img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover;display:block}.notification-logo{flex-shrink:0}.notification-unread-dot{width:8px;height:8px;border-radius:50%;background:var(--color-accent, #6B5FF5);flex-shrink:0;margin-top:6px}\n"] }]
29471
+ }] });
28628
29472
 
28629
29473
  /**
28630
29474
  * PostsService
@@ -29151,6 +29995,654 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
29151
29995
  }]
29152
29996
  }], ctorParameters: () => [] });
29153
29997
 
29998
+ class DsMobileBookingDetailSheetComponent {
29999
+ modalController;
30000
+ /** `sheet` = bottom sheet. `modal` = `ds-modal-base` shell (active + history bookings). */
30001
+ presentation = 'sheet';
30002
+ data;
30003
+ /** When true the modal sizes to its content instead of filling the screen. */
30004
+ autoHeight = false;
30005
+ get isModalPresentation() {
30006
+ return this.presentation === 'modal';
30007
+ }
30008
+ constructor(modalController) {
30009
+ this.modalController = modalController;
30010
+ }
30011
+ close() {
30012
+ this.modalController.dismiss(null, 'backdrop');
30013
+ }
30014
+ cancelBooking() {
30015
+ this.modalController.dismiss(null, 'cancel');
30016
+ }
30017
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component });
30018
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileBookingDetailSheetComponent, isStandalone: true, selector: "ds-mobile-booking-detail-sheet", inputs: { presentation: "presentation", data: "data", autoHeight: "autoHeight" }, host: { properties: { "class.presentation-modal": "this.isModalPresentation" } }, ngImport: i0, template: `
30019
+ @if (presentation === 'modal') {
30020
+ <ds-mobile-modal-base
30021
+ [headerTitle]="data.facilityTitle"
30022
+ [closeButtonLabel]="'Luk'"
30023
+ [isAutoHeight]="autoHeight"
30024
+ [keyboardContentBehavior]="'follow'">
30025
+
30026
+ <ds-mobile-section
30027
+ [showBorder]="false"
30028
+ padding="20px 20px 0 20px">
30029
+ <div class="hero-image-container">
30030
+ @if (data.heroImage) {
30031
+ <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
30032
+ } @else {
30033
+ <div class="hero-image-placeholder">
30034
+ <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
30035
+ </div>
30036
+ }
30037
+ </div>
30038
+ </ds-mobile-section>
30039
+
30040
+ <ds-mobile-section padding="20px 20px 20px 20px">
30041
+ <div class="booking-summary-card">
30042
+ <div class="details-section">
30043
+ <h3 class="details-heading">Booking detaljer</h3>
30044
+ <div class="info-rows">
30045
+ @if (data.bookingDate) {
30046
+ <div class="info-row">
30047
+ <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
30048
+ <span>{{ data.bookingDate }}</span>
30049
+ </div>
30050
+ }
30051
+ @if (data.bookingTime) {
30052
+ <div class="info-row">
30053
+ <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
30054
+ <span>{{ data.bookingTime }}</span>
30055
+ </div>
30056
+ }
30057
+ @if (data.price) {
30058
+ <div class="info-row">
30059
+ <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
30060
+ <span>{{ data.price }}</span>
30061
+ </div>
30062
+ }
30063
+ @if (data.bookingType) {
30064
+ <div class="info-row">
30065
+ <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
30066
+ <span>{{ data.bookingType }}</span>
30067
+ </div>
30068
+ }
30069
+ @for (req of data.requirements || []; track req) {
30070
+ <div class="info-row">
30071
+ <ds-icon name="remixLockLine" size="16px" color="tertiary" />
30072
+ <span>{{ req }}</span>
30073
+ </div>
30074
+ }
30075
+ </div>
30076
+ </div>
30077
+ </div>
30078
+ </ds-mobile-section>
30079
+
30080
+ @if (data.canCancel) {
30081
+ <div class="booking-action">
30082
+ <div class="booking-actions-row">
30083
+ <ds-button
30084
+ class="cancel-primary"
30085
+ size="md"
30086
+ variant="secondary"
30087
+ (clicked)="cancelBooking()">
30088
+ Annuller booking
30089
+ </ds-button>
30090
+ </div>
30091
+ </div>
30092
+ }
30093
+ </ds-mobile-modal-base>
30094
+ } @else {
30095
+ <ds-mobile-bottom-sheet-wrapper [showDragHandle]="true">
30096
+ <div class="detail-header">
30097
+ <h2 class="detail-title">{{ data.facilityTitle }}</h2>
30098
+ <ds-icon-button
30099
+ icon="remixCloseLine"
30100
+ variant="ghost"
30101
+ size="sm"
30102
+ (clicked)="close()"
30103
+ aria-label="Luk"
30104
+ />
30105
+ </div>
30106
+
30107
+ <div class="detail-content">
30108
+ <div class="hero-image-container">
30109
+ @if (data.heroImage) {
30110
+ <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
30111
+ } @else {
30112
+ <div class="hero-image-placeholder">
30113
+ <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
30114
+ </div>
30115
+ }
30116
+ </div>
30117
+
30118
+ <div class="booking-summary-card">
30119
+ <div class="details-section">
30120
+ <h3 class="details-heading">Booking detaljer</h3>
30121
+ <div class="info-rows">
30122
+ @if (data.bookingDate) {
30123
+ <div class="info-row">
30124
+ <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
30125
+ <span>{{ data.bookingDate }}</span>
30126
+ </div>
30127
+ }
30128
+ @if (data.bookingTime) {
30129
+ <div class="info-row">
30130
+ <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
30131
+ <span>{{ data.bookingTime }}</span>
30132
+ </div>
30133
+ }
30134
+ @if (data.price) {
30135
+ <div class="info-row">
30136
+ <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
30137
+ <span>{{ data.price }}</span>
30138
+ </div>
30139
+ }
30140
+ @if (data.bookingType) {
30141
+ <div class="info-row">
30142
+ <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
30143
+ <span>{{ data.bookingType }}</span>
30144
+ </div>
30145
+ }
30146
+ @for (req of data.requirements || []; track req) {
30147
+ <div class="info-row">
30148
+ <ds-icon name="remixLockLine" size="16px" color="tertiary" />
30149
+ <span>{{ req }}</span>
30150
+ </div>
30151
+ }
30152
+ </div>
30153
+ </div>
30154
+ </div>
30155
+
30156
+ @if (data.canCancel) {
30157
+ <div class="footer-button-container">
30158
+ <div class="booking-actions-column">
30159
+ <ds-button
30160
+ size="md"
30161
+ variant="secondary"
30162
+ (clicked)="cancelBooking()">
30163
+ Annuller booking
30164
+ </ds-button>
30165
+ </div>
30166
+ </div>
30167
+ }
30168
+ </div>
30169
+ </ds-mobile-bottom-sheet-wrapper>
30170
+ }
30171
+ `, isInline: true, styles: [":host{display:block;height:auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 8px}.detail-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px}.detail-content{padding:8px 20px 24px;display:flex;flex-direction:column;gap:20px}.hero-image-container{width:100%;aspect-ratio:16 / 9;border-radius:12px;overflow:hidden;background:var(--color-surface-secondary, #f5f5f5)}.hero-image{width:100%;height:100%;object-fit:cover;display:block}.hero-image-placeholder{width:100%;height:100%;min-height:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,color-mix(in srgb,var(--color-accent, #6B5FF5) 30%,transparent),color-mix(in srgb,var(--color-accent, #6B5FF5) 60%,transparent))}.booking-summary-card{background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:16px;padding:16px}.details-section{display:flex;flex-direction:column;gap:12px}.details-heading{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0}.info-rows{display:flex;flex-direction:column;gap:8px}.info-row{display:flex;align-items:center;gap:10px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #545B66);line-height:1.4}.info-row ds-icon{flex-shrink:0}.footer-button-container{width:100%;padding:16px 20px 0}.footer-button-container ::ng-deep ds-button{display:block;width:100%}.footer-button-container ::ng-deep ds-button button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px}.booking-action{padding:16px 20px;padding-bottom:calc(16px + max(8px,env(safe-area-inset-bottom,0px) - 24px));background:var(--color-surface-primary, #ffffff)}.booking-actions-row{display:flex;flex-direction:row;align-items:center;gap:12px;width:100%}.booking-action .cancel-primary{flex:1;min-width:0;display:block}.booking-action .cancel-primary ::ng-deep button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px;padding-left:16px;padding-right:16px}:host.presentation-modal{display:block;height:auto;width:100%;min-height:0;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsMobileBottomSheetWrapperComponent, selector: "ds-mobile-bottom-sheet-wrapper", inputs: ["showDragHandle"] }, { kind: "component", type: DsMobileModalBaseComponent, selector: "ds-mobile-modal-base", inputs: ["headerTitleInteractive", "showHeader"], outputs: ["titleClick"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }] });
30172
+ }
30173
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetComponent, decorators: [{
30174
+ type: Component,
30175
+ args: [{ selector: 'ds-mobile-booking-detail-sheet', standalone: true, imports: [
30176
+ CommonModule,
30177
+ DsMobileBottomSheetWrapperComponent,
30178
+ DsMobileModalBaseComponent,
30179
+ DsMobileSectionComponent,
30180
+ DsButtonComponent,
30181
+ DsIconComponent,
30182
+ DsIconButtonComponent,
30183
+ ], template: `
30184
+ @if (presentation === 'modal') {
30185
+ <ds-mobile-modal-base
30186
+ [headerTitle]="data.facilityTitle"
30187
+ [closeButtonLabel]="'Luk'"
30188
+ [isAutoHeight]="autoHeight"
30189
+ [keyboardContentBehavior]="'follow'">
30190
+
30191
+ <ds-mobile-section
30192
+ [showBorder]="false"
30193
+ padding="20px 20px 0 20px">
30194
+ <div class="hero-image-container">
30195
+ @if (data.heroImage) {
30196
+ <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
30197
+ } @else {
30198
+ <div class="hero-image-placeholder">
30199
+ <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
30200
+ </div>
30201
+ }
30202
+ </div>
30203
+ </ds-mobile-section>
30204
+
30205
+ <ds-mobile-section padding="20px 20px 20px 20px">
30206
+ <div class="booking-summary-card">
30207
+ <div class="details-section">
30208
+ <h3 class="details-heading">Booking detaljer</h3>
30209
+ <div class="info-rows">
30210
+ @if (data.bookingDate) {
30211
+ <div class="info-row">
30212
+ <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
30213
+ <span>{{ data.bookingDate }}</span>
30214
+ </div>
30215
+ }
30216
+ @if (data.bookingTime) {
30217
+ <div class="info-row">
30218
+ <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
30219
+ <span>{{ data.bookingTime }}</span>
30220
+ </div>
30221
+ }
30222
+ @if (data.price) {
30223
+ <div class="info-row">
30224
+ <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
30225
+ <span>{{ data.price }}</span>
30226
+ </div>
30227
+ }
30228
+ @if (data.bookingType) {
30229
+ <div class="info-row">
30230
+ <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
30231
+ <span>{{ data.bookingType }}</span>
30232
+ </div>
30233
+ }
30234
+ @for (req of data.requirements || []; track req) {
30235
+ <div class="info-row">
30236
+ <ds-icon name="remixLockLine" size="16px" color="tertiary" />
30237
+ <span>{{ req }}</span>
30238
+ </div>
30239
+ }
30240
+ </div>
30241
+ </div>
30242
+ </div>
30243
+ </ds-mobile-section>
30244
+
30245
+ @if (data.canCancel) {
30246
+ <div class="booking-action">
30247
+ <div class="booking-actions-row">
30248
+ <ds-button
30249
+ class="cancel-primary"
30250
+ size="md"
30251
+ variant="secondary"
30252
+ (clicked)="cancelBooking()">
30253
+ Annuller booking
30254
+ </ds-button>
30255
+ </div>
30256
+ </div>
30257
+ }
30258
+ </ds-mobile-modal-base>
30259
+ } @else {
30260
+ <ds-mobile-bottom-sheet-wrapper [showDragHandle]="true">
30261
+ <div class="detail-header">
30262
+ <h2 class="detail-title">{{ data.facilityTitle }}</h2>
30263
+ <ds-icon-button
30264
+ icon="remixCloseLine"
30265
+ variant="ghost"
30266
+ size="sm"
30267
+ (clicked)="close()"
30268
+ aria-label="Luk"
30269
+ />
30270
+ </div>
30271
+
30272
+ <div class="detail-content">
30273
+ <div class="hero-image-container">
30274
+ @if (data.heroImage) {
30275
+ <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
30276
+ } @else {
30277
+ <div class="hero-image-placeholder">
30278
+ <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
30279
+ </div>
30280
+ }
30281
+ </div>
30282
+
30283
+ <div class="booking-summary-card">
30284
+ <div class="details-section">
30285
+ <h3 class="details-heading">Booking detaljer</h3>
30286
+ <div class="info-rows">
30287
+ @if (data.bookingDate) {
30288
+ <div class="info-row">
30289
+ <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
30290
+ <span>{{ data.bookingDate }}</span>
30291
+ </div>
30292
+ }
30293
+ @if (data.bookingTime) {
30294
+ <div class="info-row">
30295
+ <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
30296
+ <span>{{ data.bookingTime }}</span>
30297
+ </div>
30298
+ }
30299
+ @if (data.price) {
30300
+ <div class="info-row">
30301
+ <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
30302
+ <span>{{ data.price }}</span>
30303
+ </div>
30304
+ }
30305
+ @if (data.bookingType) {
30306
+ <div class="info-row">
30307
+ <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
30308
+ <span>{{ data.bookingType }}</span>
30309
+ </div>
30310
+ }
30311
+ @for (req of data.requirements || []; track req) {
30312
+ <div class="info-row">
30313
+ <ds-icon name="remixLockLine" size="16px" color="tertiary" />
30314
+ <span>{{ req }}</span>
30315
+ </div>
30316
+ }
30317
+ </div>
30318
+ </div>
30319
+ </div>
30320
+
30321
+ @if (data.canCancel) {
30322
+ <div class="footer-button-container">
30323
+ <div class="booking-actions-column">
30324
+ <ds-button
30325
+ size="md"
30326
+ variant="secondary"
30327
+ (clicked)="cancelBooking()">
30328
+ Annuller booking
30329
+ </ds-button>
30330
+ </div>
30331
+ </div>
30332
+ }
30333
+ </div>
30334
+ </ds-mobile-bottom-sheet-wrapper>
30335
+ }
30336
+ `, styles: [":host{display:block;height:auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 8px}.detail-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px}.detail-content{padding:8px 20px 24px;display:flex;flex-direction:column;gap:20px}.hero-image-container{width:100%;aspect-ratio:16 / 9;border-radius:12px;overflow:hidden;background:var(--color-surface-secondary, #f5f5f5)}.hero-image{width:100%;height:100%;object-fit:cover;display:block}.hero-image-placeholder{width:100%;height:100%;min-height:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,color-mix(in srgb,var(--color-accent, #6B5FF5) 30%,transparent),color-mix(in srgb,var(--color-accent, #6B5FF5) 60%,transparent))}.booking-summary-card{background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:16px;padding:16px}.details-section{display:flex;flex-direction:column;gap:12px}.details-heading{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0}.info-rows{display:flex;flex-direction:column;gap:8px}.info-row{display:flex;align-items:center;gap:10px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #545B66);line-height:1.4}.info-row ds-icon{flex-shrink:0}.footer-button-container{width:100%;padding:16px 20px 0}.footer-button-container ::ng-deep ds-button{display:block;width:100%}.footer-button-container ::ng-deep ds-button button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px}.booking-action{padding:16px 20px;padding-bottom:calc(16px + max(8px,env(safe-area-inset-bottom,0px) - 24px));background:var(--color-surface-primary, #ffffff)}.booking-actions-row{display:flex;flex-direction:row;align-items:center;gap:12px;width:100%}.booking-action .cancel-primary{flex:1;min-width:0;display:block}.booking-action .cancel-primary ::ng-deep button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px;padding-left:16px;padding-right:16px}:host.presentation-modal{display:block;height:auto;width:100%;min-height:0;overflow:hidden}\n"] }]
30337
+ }], ctorParameters: () => [{ type: i1.ModalController }], propDecorators: { presentation: [{
30338
+ type: Input
30339
+ }], data: [{
30340
+ type: Input
30341
+ }], autoHeight: [{
30342
+ type: Input
30343
+ }], isModalPresentation: [{
30344
+ type: HostBinding,
30345
+ args: ['class.presentation-modal']
30346
+ }] } });
30347
+
30348
+ class DsMobileBookingDetailSheetService extends BaseModalService {
30349
+ bottomSheet;
30350
+ constructor(modalController, bottomSheet) {
30351
+ super(modalController);
30352
+ this.bottomSheet = bottomSheet;
30353
+ }
30354
+ /**
30355
+ * Bottom-sheet presentation (draggable breakpoints). Prefer `openAsModal` for booking lists.
30356
+ */
30357
+ async open(data) {
30358
+ const modal = await this.bottomSheet.create({
30359
+ component: DsMobileBookingDetailSheetComponent,
30360
+ componentProps: { data },
30361
+ autoHeight: true,
30362
+ backdropDismiss: true,
30363
+ backdropBlur: true,
30364
+ });
30365
+ const result = await modal.onWillDismiss();
30366
+ return { role: result.role, data: result.data ?? undefined };
30367
+ }
30368
+ /** `ds-modal-base` shell — used for active and past bookings. Auto-heights to content. */
30369
+ async openAsModal(data) {
30370
+ const modal = await this.createModal(DsMobileBookingDetailSheetComponent, { data, presentation: 'modal', autoHeight: true }, { keyboardClose: true, autoHeight: true });
30371
+ await modal.present();
30372
+ const result = await modal.onWillDismiss();
30373
+ return { role: result.role, data: result.data ?? undefined };
30374
+ }
30375
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, deps: [{ token: i1.ModalController }, { token: DsMobileBottomSheetService }], target: i0.ɵɵFactoryTarget.Injectable });
30376
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, providedIn: 'root' });
30377
+ }
30378
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, decorators: [{
30379
+ type: Injectable,
30380
+ args: [{
30381
+ providedIn: 'root',
30382
+ }]
30383
+ }], ctorParameters: () => [{ type: i1.ModalController }, { type: DsMobileBottomSheetService }] });
30384
+
30385
+ const VENDOR_MODAL_SERVICE = new InjectionToken('VendorModalService');
30386
+ const BOOKING_DETAIL_MAP = {
30387
+ 'booking-1': {
30388
+ id: 'booking-1',
30389
+ facilityTitle: 'Festlokale på taget',
30390
+ heroImage: '/Assets/Dummy-photos/rooftop-party.jpg',
30391
+ bookingDate: '14. februar',
30392
+ bookingTime: '9:00 - 17:00',
30393
+ price: '200 kr. per booking',
30394
+ canCancel: true,
30395
+ },
30396
+ 'booking-2': {
30397
+ id: 'booking-2',
30398
+ facilityTitle: 'Gæsteparkering',
30399
+ heroImage: '/Assets/Dummy-photos/parking.jpg',
30400
+ bookingDate: '20. marts',
30401
+ bookingTime: '8:00 - 20:00',
30402
+ price: '50 kr. per booking',
30403
+ canCancel: true,
30404
+ },
30405
+ 'booking-3': {
30406
+ id: 'booking-3',
30407
+ facilityTitle: 'Boremaskinen',
30408
+ heroImage: '/Assets/Dummy-photos/handyman.jpg',
30409
+ bookingDate: '25. marts',
30410
+ bookingTime: '10:00 - 14:00',
30411
+ price: 'Gratis',
30412
+ canCancel: true,
30413
+ },
30414
+ };
30415
+ const VENDOR_DETAIL_MAP = {
30416
+ 'v-1': {
30417
+ vendorName: 'CleanTeam ApS',
30418
+ vendorDescription: '<h3>Rengøring</h3><p>CleanTeam ApS tilbyder professionel trappevask og vinduespolering for ejendommen. Servicen udføres ugentligt og kan tilpasses jeres behov.</p><h3>Pris</h3><p>350 kr. per besøg</p>',
30419
+ vendorImage: '/Assets/Dummy-photos/clean-team.jpg',
30420
+ vendorLogo: '/Assets/dummy-logos/cleanteam-logo.svg',
30421
+ },
30422
+ 'v-2': {
30423
+ vendorName: 'Nordisk Rengøring',
30424
+ vendorDescription: '<h3>Rengøring</h3><p>Nordisk Rengøring leverer fleksibel erhvervs- og boligrengøring. Vi tilpasser rengøringen til jeres specifikke behov.</p><h3>Pris</h3><p>275 kr. per time</p>',
30425
+ vendorImage: '/Assets/Dummy-photos/nordic-cleaning.jpg',
30426
+ vendorLogo: '/Assets/dummy-logos/nordiccleaning-logo.svg',
30427
+ },
30428
+ 'v-3': {
30429
+ vendorName: 'Blik Partner A/S',
30430
+ vendorDescription: '<h3>VVS</h3><p>BlikPartner A/S er jeres VVS-partner til alt fra løbende vedligeholdelse til akutte udkald. Vi dækker reparation af vandrør, afløb, radiatorer og blandingsbatterier.</p><h3>Pris</h3><p>450 kr. per udkald + tid</p>',
30431
+ vendorImage: '/Assets/Dummy-photos/plumbing.jpg',
30432
+ },
30433
+ 'v-4': {
30434
+ vendorName: 'ElektroTek ApS',
30435
+ vendorDescription: '<h3>Elektriker</h3><p>ElektroTek ApS varetager el-installationer, fejlsøgning og lovpligtige eftersyn for ejendommen. Vi udfører også småopgaver som udskiftning af kontakter og lampeudtag.</p><h3>Pris</h3><p>395 kr. per time</p>',
30436
+ vendorImage: '/Assets/Dummy-photos/electrician.jpg',
30437
+ vendorLogo: '/Assets/dummy-logos/electrician-logo.svg',
30438
+ },
30439
+ 'v-5': {
30440
+ vendorName: 'HaveService Danmark',
30441
+ vendorDescription: '<h3>Have & Grønne arealer</h3><p>HaveService Danmark står for den løbende pleje af fællesarealer og haver — herunder græsslåning, hækklipning, ukrudtsbekæmpelse og sæsonbeplantning.</p><h3>Pris</h3><p>500 kr. per besøg</p>',
30442
+ vendorImage: '/Assets/Dummy-photos/gardener.jpg',
30443
+ vendorLogo: '/Assets/dummy-logos/gardener-logo.svg',
30444
+ },
30445
+ };
30446
+ class DsMobileNotificationModalService extends BaseModalService {
30447
+ router = inject(Router);
30448
+ notificationService = inject(NotificationService);
30449
+ postsService = inject(PostsService);
30450
+ userService = inject(UserService);
30451
+ postModal = inject(DsMobilePostDetailModalService);
30452
+ peerMessaging = inject(PeerMessagingService);
30453
+ peerChat = inject(PeerChatLauncherService);
30454
+ bookingDetailSheet = inject(DsMobileBookingDetailSheetService);
30455
+ vendorModal = inject(VENDOR_MODAL_SERVICE, { optional: true });
30456
+ constructor(modalController) {
30457
+ super(modalController);
30458
+ }
30459
+ /**
30460
+ * Open the notification modal. The modal reads notifications reactively
30461
+ * from NotificationService, so it updates live when items are marked
30462
+ * read or removed via long-press actions.
30463
+ *
30464
+ * Returns the tapped notification (if the user navigated to one), or null.
30465
+ */
30466
+ async open() {
30467
+ const modal = await this.createModal(DsMobileNotificationModalComponent, {});
30468
+ await modal.present();
30469
+ const result = await modal.onWillDismiss();
30470
+ const tapped = result.data ?? null;
30471
+ if (tapped) {
30472
+ this.notificationService.markAsRead(tapped.id);
30473
+ await this.routeTo(tapped);
30474
+ }
30475
+ return tapped;
30476
+ }
30477
+ /**
30478
+ * Navigate to the screen associated with a notification item.
30479
+ * Public so downstream devs can call it from a Capacitor
30480
+ * `pushNotificationActionPerformed` listener for deep-link handling.
30481
+ */
30482
+ async routeTo(item) {
30483
+ const id = item.targetId;
30484
+ switch (item.type) {
30485
+ case 'inquiry_update':
30486
+ case 'inquiry_assigned':
30487
+ case 'inquiry_resolved':
30488
+ await this.router.navigate(['/inquiry-detail', id ?? '1']);
30489
+ break;
30490
+ case 'community_post':
30491
+ case 'community_comment':
30492
+ case 'community_like':
30493
+ case 'community_mention':
30494
+ if (id) {
30495
+ await this.openPostModal(id);
30496
+ }
30497
+ else {
30498
+ await this.router.navigate(['/announcements']);
30499
+ }
30500
+ break;
30501
+ case 'booking_confirmed':
30502
+ case 'booking_cancelled':
30503
+ case 'booking_reminder':
30504
+ case 'facility_available':
30505
+ if (id) {
30506
+ await this.openBookingDetail(item);
30507
+ }
30508
+ else {
30509
+ await this.router.navigate(['/booking']);
30510
+ }
30511
+ break;
30512
+ case 'message_new':
30513
+ case 'message_group':
30514
+ if (id) {
30515
+ await this.openChat(id);
30516
+ }
30517
+ else {
30518
+ await this.router.navigate(['/messages']);
30519
+ }
30520
+ break;
30521
+ case 'service_update':
30522
+ if (id) {
30523
+ await this.openVendorModal(id);
30524
+ }
30525
+ else {
30526
+ await this.router.navigate(['/services']);
30527
+ }
30528
+ break;
30529
+ case 'handbook_update':
30530
+ await this.router.navigate(['/handbook']);
30531
+ break;
30532
+ case 'family_access':
30533
+ await this.router.navigate(['/family-access']);
30534
+ break;
30535
+ default:
30536
+ await this.router.navigate(['/home']);
30537
+ break;
30538
+ }
30539
+ }
30540
+ async openChat(conversationId) {
30541
+ const conv = this.peerMessaging.conversations().find(c => c.id === conversationId);
30542
+ if (conv) {
30543
+ await this.peerChat.openConversation(conv);
30544
+ }
30545
+ else {
30546
+ await this.router.navigate(['/messages']);
30547
+ }
30548
+ }
30549
+ async openVendorModal(vendorId) {
30550
+ const vendor = VENDOR_DETAIL_MAP[vendorId];
30551
+ if (vendor && this.vendorModal) {
30552
+ await this.vendorModal.open(vendor);
30553
+ }
30554
+ else {
30555
+ await this.router.navigate(['/services']);
30556
+ }
30557
+ }
30558
+ async openBookingDetail(item) {
30559
+ const booking = BOOKING_DETAIL_MAP[item.targetId];
30560
+ if (booking) {
30561
+ await this.bookingDetailSheet.openAsModal(booking);
30562
+ }
30563
+ else {
30564
+ await this.router.navigate(['/booking']);
30565
+ }
30566
+ }
30567
+ async openPostModal(postId) {
30568
+ const post = this.postsService.getPostById(postId);
30569
+ if (!post) {
30570
+ await this.router.navigate(['/announcements']);
30571
+ return;
30572
+ }
30573
+ const data = {
30574
+ postId: post.id,
30575
+ authorName: post.authorName,
30576
+ authorRole: post.authorRole,
30577
+ timestamp: post.timestamp,
30578
+ content: post.content,
30579
+ avatarInitials: post.avatarInitials,
30580
+ avatarType: post.avatarType === 'icon' ? undefined : post.avatarType,
30581
+ avatarSrc: post.avatarSrc,
30582
+ imageSrc: post.imageSrc,
30583
+ imageAlt: post.imageAlt,
30584
+ isLiked: post.isLiked,
30585
+ likeCount: post.likeCount,
30586
+ commentCount: post.commentCount,
30587
+ comments: post.comments,
30588
+ };
30589
+ await this.postModal.open(data, {
30590
+ currentUserName: this.userService.displayName(),
30591
+ currentUserInitials: this.userService.avatarInitials(),
30592
+ });
30593
+ }
30594
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalService, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Injectable });
30595
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalService, providedIn: 'root' });
30596
+ }
30597
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalService, decorators: [{
30598
+ type: Injectable,
30599
+ args: [{
30600
+ providedIn: 'root',
30601
+ }]
30602
+ }], ctorParameters: () => [{ type: i1.ModalController }] });
30603
+
30604
+ class DsMobileToggleComponent {
30605
+ checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
30606
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
30607
+ changed = output();
30608
+ handleClick(event) {
30609
+ event.stopPropagation();
30610
+ this.toggle();
30611
+ }
30612
+ toggle() {
30613
+ if (this.disabled())
30614
+ return;
30615
+ const next = !this.checked();
30616
+ this.checked.set(next);
30617
+ this.changed.emit(next);
30618
+ }
30619
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
30620
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DsMobileToggleComponent, isStandalone: true, selector: "ds-mobile-toggle", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", changed: "changed" }, host: { listeners: { "click": "handleClick($event)", "keydown.enter": "toggle()", "keydown.space": "$event.preventDefault(); toggle()" }, properties: { "class.disabled": "disabled()", "attr.role": "\"switch\"", "attr.aria-checked": "checked()", "attr.aria-disabled": "disabled() || null", "attr.tabindex": "disabled() ? -1 : 0" } }, ngImport: i0, template: `
30621
+ <div class="track" [class.active]="checked()">
30622
+ <div class="knob"></div>
30623
+ </div>
30624
+ `, isInline: true, styles: [":host{display:inline-flex;cursor:pointer;-webkit-tap-highlight-color:transparent;flex-shrink:0;outline:none}:host(.disabled){opacity:.4;pointer-events:none}:host:focus-visible .track{outline:2px solid var(--color-brand-primary, #5d5fef);outline-offset:2px}.track{width:40px;height:24px;background:var(--color-background-neutral-tertiary, #e3e6eb);border-radius:var(--border-radius-round, 1000px);padding:4px;box-sizing:border-box;transition:background .2s ease}.track.active{background:var(--color-accent, #6b5ff5)}.knob{width:16px;height:16px;background:var(--color-background-neutral-primary, white);border-radius:var(--border-radius-round, 1000px);transition:transform .2s ease}.track.active .knob{transform:translate(16px)}\n"] });
30625
+ }
30626
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileToggleComponent, decorators: [{
30627
+ type: Component,
30628
+ args: [{ selector: 'ds-mobile-toggle', standalone: true, host: {
30629
+ '(click)': 'handleClick($event)',
30630
+ '[class.disabled]': 'disabled()',
30631
+ '[attr.role]': '"switch"',
30632
+ '[attr.aria-checked]': 'checked()',
30633
+ '[attr.aria-disabled]': 'disabled() || null',
30634
+ '[attr.tabindex]': 'disabled() ? -1 : 0',
30635
+ '(keydown.enter)': 'toggle()',
30636
+ '(keydown.space)': '$event.preventDefault(); toggle()',
30637
+ }, template: `
30638
+ <div class="track" [class.active]="checked()">
30639
+ <div class="knob"></div>
30640
+ </div>
30641
+ `, styles: [":host{display:inline-flex;cursor:pointer;-webkit-tap-highlight-color:transparent;flex-shrink:0;outline:none}:host(.disabled){opacity:.4;pointer-events:none}:host:focus-visible .track{outline:2px solid var(--color-brand-primary, #5d5fef);outline-offset:2px}.track{width:40px;height:24px;background:var(--color-background-neutral-tertiary, #e3e6eb);border-radius:var(--border-radius-round, 1000px);padding:4px;box-sizing:border-box;transition:background .2s ease}.track.active{background:var(--color-accent, #6b5ff5)}.knob{width:16px;height:16px;background:var(--color-background-neutral-primary, white);border-radius:var(--border-radius-round, 1000px);transition:transform .2s ease}.track.active .knob{transform:translate(16px)}\n"] }]
30642
+ }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
30643
+
30644
+ // Mobile Page Components
30645
+
29154
30646
  class MobileCommunityPageComponent {
29155
30647
  router;
29156
30648
  route;
@@ -29161,6 +30653,8 @@ class MobileCommunityPageComponent {
29161
30653
  postsService;
29162
30654
  pageComponent;
29163
30655
  pinnedSwiper;
30656
+ notificationModal = inject(DsMobileNotificationModalService);
30657
+ notificationService = inject(NotificationService);
29164
30658
  // Get posts from service (using computed for safe initialization)
29165
30659
  allPosts = computed(() => this.postsService.posts(), ...(ngDevMode ? [{ debugName: "allPosts" }] : []));
29166
30660
  // Get pinned posts - filter by isPinned flag
@@ -29203,6 +30697,11 @@ class MobileCommunityPageComponent {
29203
30697
  // Complete the infinite scroll
29204
30698
  event.target.complete();
29205
30699
  }
30700
+ async handleNotificationClick() {
30701
+ const tapped = await this.notificationModal.open();
30702
+ if (tapped)
30703
+ console.log('Notification tapped:', tapped.type, tapped.id);
30704
+ }
29206
30705
  handleRefresh(event) {
29207
30706
  console.log('Pull-to-refresh triggered');
29208
30707
  // Check if offline and complete immediately
@@ -29444,8 +30943,10 @@ class MobileCommunityPageComponent {
29444
30943
  <ds-mobile-page-main
29445
30944
  #pageComponent
29446
30945
  title="Fællesskab"
30946
+ [notificationCount]="notificationService.unreadCount()"
29447
30947
  [avatarInitials]="userService.avatarInitials()"
29448
30948
  [avatarType]="userService.avatarType()"
30949
+ (notificationClick)="handleNotificationClick()"
29449
30950
  (refresh)="handleRefresh($event)">
29450
30951
 
29451
30952
  <!-- Offline indicator -->
@@ -29698,7 +31199,7 @@ class MobileCommunityPageComponent {
29698
31199
  }
29699
31200
  </ds-mobile-section>
29700
31201
  </ds-mobile-page-main>
29701
- `, isInline: true, styles: [".pinned-posts-swiper-wrapper{padding:0;position:relative}.swiper-nav-buttons{display:contents}.swiper-nav-button{position:absolute;top:50%;transform:translateY(-50%);z-index:10}.swiper-nav-button:first-child{left:-48px}.swiper-nav-button:last-child{right:-48px}::ng-deep .swiper-nav-button button{border-radius:50%!important;width:48px!important;height:48px!important;padding:0!important}@media (max-width: 767px){.swiper-nav-buttons{display:none}}::ng-deep .pinned-posts-swiper .swiper-slide{width:100%;max-width:600px;height:auto}@media (min-width: 768px){::ng-deep .pinned-posts-swiper .swiper-slide{max-width:100%}}.swiper-post-item{width:100%;height:auto}::ng-deep .pinned-posts-swiper .swiper-slide ds-mobile-interactive-list-item-post{height:auto}::ng-deep .pinned-posts-swiper .swiper-wrapper{height:auto;align-items:flex-start}.post-list-wrapper{display:flex;flex-direction:column}.clickable-image{cursor:pointer;transition:transform .2s ease,opacity .2s ease;border-radius:8px;display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.clickable-image:active{transform:scale(.98);opacity:.9}.community-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:16px 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}ion-infinite-scroll{--color: var(--color-primary-surface)}ion-infinite-scroll-content{--color: var(--color-primary-surface)}ion-infinite-scroll-content::part(spinner){color:var(--color-primary-surface)}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "align", "clickable", "enableLongPress", "moreActions"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobilePostComposerComponent, selector: "ds-mobile-post-composer", inputs: ["avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "placeholder", "buttonText"], outputs: ["composerClick"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostMediaComponent, selector: "post-media" }, { kind: "component", type: PostAttachmentsComponent, selector: "post-attachments" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }, { kind: "component", type: DsMobileCardInlineFileComponent, selector: "ds-mobile-card-inline-file", inputs: ["fileName", "fileSize", "variant", "layout", "fileUrl"], outputs: ["fileClick"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileInlinePhotoComponent, selector: "ds-mobile-inline-photo", inputs: ["images", "loadingStates", "author", "maxVisible", "useGrid"], outputs: ["photoClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: IonInfiniteScroll, selector: "ion-infinite-scroll", inputs: ["disabled", "position", "threshold"] }, { kind: "component", type: IonInfiniteScrollContent, selector: "ion-infinite-scroll-content", inputs: ["loadingSpinner", "loadingText"] }] });
31202
+ `, isInline: true, styles: [".pinned-posts-swiper-wrapper{padding:0;position:relative}.swiper-nav-buttons{display:contents}.swiper-nav-button{position:absolute;top:50%;transform:translateY(-50%);z-index:10}.swiper-nav-button:first-child{left:-48px}.swiper-nav-button:last-child{right:-48px}::ng-deep .swiper-nav-button button{border-radius:50%!important;width:48px!important;height:48px!important;padding:0!important}@media (max-width: 767px){.swiper-nav-buttons{display:none}}::ng-deep .pinned-posts-swiper .swiper-slide{width:100%;max-width:600px;height:auto}@media (min-width: 768px){::ng-deep .pinned-posts-swiper .swiper-slide{max-width:100%}}.swiper-post-item{width:100%;height:auto}::ng-deep .pinned-posts-swiper .swiper-slide ds-mobile-interactive-list-item-post{height:auto}::ng-deep .pinned-posts-swiper .swiper-wrapper{height:auto;align-items:flex-start}.post-list-wrapper{display:flex;flex-direction:column}.clickable-image{cursor:pointer;transition:transform .2s ease,opacity .2s ease;border-radius:8px;display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.clickable-image:active{transform:scale(.98);opacity:.9}.community-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:16px 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}ion-infinite-scroll{--color: var(--color-primary-surface)}ion-infinite-scroll-content{--color: var(--color-primary-surface)}ion-infinite-scroll-content::part(spinner){color:var(--color-primary-surface)}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showNotification", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "align", "clickable", "enableLongPress", "moreActions"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobilePostComposerComponent, selector: "ds-mobile-post-composer", inputs: ["avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "placeholder", "buttonText"], outputs: ["composerClick"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostMediaComponent, selector: "post-media" }, { kind: "component", type: PostAttachmentsComponent, selector: "post-attachments" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }, { kind: "component", type: DsMobileCardInlineFileComponent, selector: "ds-mobile-card-inline-file", inputs: ["fileName", "fileSize", "variant", "layout", "fileUrl"], outputs: ["fileClick"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileInlinePhotoComponent, selector: "ds-mobile-inline-photo", inputs: ["images", "loadingStates", "author", "maxVisible", "useGrid"], outputs: ["photoClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: IonInfiniteScroll, selector: "ion-infinite-scroll", inputs: ["disabled", "position", "threshold"] }, { kind: "component", type: IonInfiniteScrollContent, selector: "ion-infinite-scroll-content", inputs: ["loadingSpinner", "loadingText"] }] });
29702
31203
  }
29703
31204
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileCommunityPageComponent, decorators: [{
29704
31205
  type: Component,
@@ -29725,8 +31226,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
29725
31226
  <ds-mobile-page-main
29726
31227
  #pageComponent
29727
31228
  title="Fællesskab"
31229
+ [notificationCount]="notificationService.unreadCount()"
29728
31230
  [avatarInitials]="userService.avatarInitials()"
29729
31231
  [avatarType]="userService.avatarType()"
31232
+ (notificationClick)="handleNotificationClick()"
29730
31233
  (refresh)="handleRefresh($event)">
29731
31234
 
29732
31235
  <!-- Offline indicator -->
@@ -29991,6 +31494,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
29991
31494
  class MobileHandbookPageComponent {
29992
31495
  userService;
29993
31496
  pageComponent;
31497
+ notificationModal = inject(DsMobileNotificationModalService);
31498
+ notificationService = inject(NotificationService);
29994
31499
  // Utilities folder data
29995
31500
  utilitiesItems = [
29996
31501
  {
@@ -30233,6 +31738,11 @@ class MobileHandbookPageComponent {
30233
31738
  constructor(userService) {
30234
31739
  this.userService = userService;
30235
31740
  }
31741
+ async handleNotificationClick() {
31742
+ const tapped = await this.notificationModal.open();
31743
+ if (tapped)
31744
+ console.log('Notification tapped:', tapped.type, tapped.id);
31745
+ }
30236
31746
  handleRefresh(event) {
30237
31747
  console.log('Pull-to-refresh triggered');
30238
31748
  // Check if offline and complete immediately
@@ -30248,7 +31758,7 @@ class MobileHandbookPageComponent {
30248
31758
  }
30249
31759
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileHandbookPageComponent, deps: [{ token: UserService }], target: i0.ɵɵFactoryTarget.Component });
30250
31760
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MobileHandbookPageComponent, isStandalone: true, selector: "app-mobile-handbook-page", viewQueries: [{ propertyName: "pageComponent", first: true, predicate: ["pageComponent"], descendants: true }], ngImport: i0, template: `
30251
- <ds-mobile-page-main #pageComponent title="Håndbog" [avatarInitials]="userService.avatarInitials()" [avatarType]="userService.avatarType()" (refresh)="handleRefresh($event)">
31761
+ <ds-mobile-page-main #pageComponent title="Håndbog" [notificationCount]="notificationService.unreadCount()" [avatarInitials]="userService.avatarInitials()" [avatarType]="userService.avatarType()" (notificationClick)="handleNotificationClick()" (refresh)="handleRefresh($event)">
30252
31762
  <!-- Offline indicator -->
30253
31763
  @if (pageComponent.isOffline()) {
30254
31764
  <ds-mobile-offline-banner offline-indicator title="Ingen internetforbindelse" message="Nogle funktioner kan være utilgængelige"> </ds-mobile-offline-banner>
@@ -30269,12 +31779,12 @@ class MobileHandbookPageComponent {
30269
31779
  </div>
30270
31780
  </ds-mobile-section>
30271
31781
  </ds-mobile-page-main>
30272
- `, isInline: true, styles: [".folders-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;justify-items:center}@media (min-width: 768px){.folders-grid{grid-template-columns:repeat(3,1fr)}}ds-mobile-handbook-folder{width:100%;min-width:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileHandbookFolderComponent, selector: "ds-mobile-handbook-folder", inputs: ["variant", "customColor", "iconName", "itemCount", "label", "items", "loading", "error"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }] });
31782
+ `, isInline: true, styles: [".folders-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;justify-items:center}@media (min-width: 768px){.folders-grid{grid-template-columns:repeat(3,1fr)}}ds-mobile-handbook-folder{width:100%;min-width:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showNotification", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileHandbookFolderComponent, selector: "ds-mobile-handbook-folder", inputs: ["variant", "customColor", "iconName", "itemCount", "label", "items", "loading", "error"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }] });
30273
31783
  }
30274
31784
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileHandbookPageComponent, decorators: [{
30275
31785
  type: Component,
30276
31786
  args: [{ selector: 'app-mobile-handbook-page', standalone: true, imports: [DsMobilePageMainComponent, DsMobileSectionComponent, DsMobileHandbookFolderComponent, DsMobileOfflineBannerComponent], template: `
30277
- <ds-mobile-page-main #pageComponent title="Håndbog" [avatarInitials]="userService.avatarInitials()" [avatarType]="userService.avatarType()" (refresh)="handleRefresh($event)">
31787
+ <ds-mobile-page-main #pageComponent title="Håndbog" [notificationCount]="notificationService.unreadCount()" [avatarInitials]="userService.avatarInitials()" [avatarType]="userService.avatarType()" (notificationClick)="handleNotificationClick()" (refresh)="handleRefresh($event)">
30278
31788
  <!-- Offline indicator -->
30279
31789
  @if (pageComponent.isOffline()) {
30280
31790
  <ds-mobile-offline-banner offline-indicator title="Ingen internetforbindelse" message="Nogle funktioner kan være utilgængelige"> </ds-mobile-offline-banner>
@@ -30851,21 +32361,51 @@ class MobileHomePageComponent {
30851
32361
  // (hides tab bar + renders loading screen at the correct DOM level)
30852
32362
  pageLoading = inject(PageLoadingService);
30853
32363
  // Get recent posts from PostsService - exclude pinned post (post-4) and limit to 3
30854
- recentPosts = computed(() => this.postsService.posts()
30855
- .filter(post => post.id !== 'post-4') // Exclude pinned post
32364
+ recentPosts = computed(() => this.postsService
32365
+ .posts()
32366
+ .filter((post) => post.id !== 'post-4') // Exclude pinned post
30856
32367
  .slice(0, 3), ...(ngDevMode ? [{ debugName: "recentPosts" }] : []));
30857
32368
  inquiriesService = inject(InquiriesService);
30858
32369
  openInquiries = computed(() => this.inquiriesService.openInquiries().slice(0, 3), ...(ngDevMode ? [{ debugName: "openInquiries" }] : []));
30859
32370
  recentPeerMessages = computed(() => this.peerMessaging.conversations().slice(0, 3), ...(ngDevMode ? [{ debugName: "recentPeerMessages" }] : []));
30860
32371
  allVendors = signal([
30861
- { id: 'v-1', name: 'CleanTeam ApS', category: 'Rengøring', description: 'Trappevask og vinduespolering', phone: '+45 70 20 30 40', logo: '/Assets/dummy-logos/cleanteam-logo.svg', thumbnail: '/Assets/Dummy-photos/clean-team.jpg', fullDescription: '<h3>Rengøring</h3><p>CleanTeam ApS tilbyder professionel trappevask og vinduespolering for ejendommen. Servicen udføres ugentligt og kan tilpasses jeres behov.</p><h3>Pris</h3><p>350 kr. per besøg</p>' },
30862
- { id: 'v-3', name: 'Blik Partner A/S', category: 'VVS', description: 'VVS-service og akut udkald', phone: '+45 33 44 55 66', thumbnail: '/Assets/Dummy-photos/plumbing.jpg', fullDescription: '<h3>VVS</h3><p>BlikPartner A/S er jeres VVS-partner til alt fra løbende vedligeholdelse til akutte udkald. Vi dækker reparation af vandrør, afløb, radiatorer og blandingsbatterier.</p><h3>Pris</h3><p>450 kr. per udkald + tid</p>' },
30863
- { id: 'v-4', name: 'ElektroTek ApS', category: 'Elektriker', description: 'El-installationer og fejlsøgning', phone: '+45 23 45 67 89', logo: '/Assets/dummy-logos/electrician-logo.svg', thumbnail: '/Assets/Dummy-photos/electrician.jpg', heroImage: '/Assets/Dummy-photos/electrician.jpg', fullDescription: '<h3>Elektriker</h3><p>ElektroTek ApS varetager el-installationer, fejlsøgning og lovpligtige eftersyn for ejendommen. Vi udfører også småopgaver som udskiftning af kontakter og lampeudtag.</p><h3>Pris</h3><p>395 kr. per time</p>' },
32372
+ {
32373
+ id: 'v-1',
32374
+ name: 'CleanTeam ApS',
32375
+ category: 'Rengøring',
32376
+ description: 'Trappevask og vinduespolering',
32377
+ phone: '+45 70 20 30 40',
32378
+ logo: '/Assets/dummy-logos/cleanteam-logo.svg',
32379
+ thumbnail: '/Assets/Dummy-photos/clean-team.jpg',
32380
+ fullDescription: '<h3>Rengøring</h3><p>CleanTeam ApS tilbyder professionel trappevask og vinduespolering for ejendommen. Servicen udføres ugentligt og kan tilpasses jeres behov.</p><h3>Pris</h3><p>350 kr. per besøg</p>',
32381
+ },
32382
+ {
32383
+ id: 'v-3',
32384
+ name: 'Blik Partner A/S',
32385
+ category: 'VVS',
32386
+ description: 'VVS-service og akut udkald',
32387
+ phone: '+45 33 44 55 66',
32388
+ thumbnail: '/Assets/Dummy-photos/plumbing.jpg',
32389
+ fullDescription: '<h3>VVS</h3><p>BlikPartner A/S er jeres VVS-partner til alt fra løbende vedligeholdelse til akutte udkald. Vi dækker reparation af vandrør, afløb, radiatorer og blandingsbatterier.</p><h3>Pris</h3><p>450 kr. per udkald + tid</p>',
32390
+ },
32391
+ {
32392
+ id: 'v-4',
32393
+ name: 'ElektroTek ApS',
32394
+ category: 'Elektriker',
32395
+ description: 'El-installationer og fejlsøgning',
32396
+ phone: '+45 23 45 67 89',
32397
+ logo: '/Assets/dummy-logos/electrician-logo.svg',
32398
+ thumbnail: '/Assets/Dummy-photos/electrician.jpg',
32399
+ heroImage: '/Assets/Dummy-photos/electrician.jpg',
32400
+ fullDescription: '<h3>Elektriker</h3><p>ElektroTek ApS varetager el-installationer, fejlsøgning og lovpligtige eftersyn for ejendommen. Vi udfører også småopgaver som udskiftning af kontakter og lampeudtag.</p><h3>Pris</h3><p>395 kr. per time</p>',
32401
+ },
30864
32402
  ], ...(ngDevMode ? [{ debugName: "allVendors" }] : []));
30865
32403
  previewVendors = computed(() => this.allVendors().slice(0, 3), ...(ngDevMode ? [{ debugName: "previewVendors" }] : []));
30866
32404
  modalCtrl = inject(ModalController);
30867
32405
  vendorModal = inject(DsMobileServiceVendorModalService);
30868
32406
  newInquiryModal = inject(DsMobileNewInquiryModalService);
32407
+ notificationModal = inject(DsMobileNotificationModalService);
32408
+ notificationService = inject(NotificationService);
30869
32409
  constructor(router, navCtrl, userService, postsService, postModal, trackingPermissionService, bottomSheet, familyAccessService, peerMessaging, peerChat, lightboxService) {
30870
32410
  this.router = router;
30871
32411
  this.navCtrl = navCtrl;
@@ -30893,6 +32433,11 @@ class MobileHomePageComponent {
30893
32433
  }, delayMs);
30894
32434
  });
30895
32435
  }
32436
+ async handleNotificationClick() {
32437
+ const tapped = await this.notificationModal.open();
32438
+ if (tapped)
32439
+ console.log('Notification tapped:', tapped.type, tapped.id);
32440
+ }
30896
32441
  handleRefresh(event) {
30897
32442
  console.log('Pull-to-refresh triggered');
30898
32443
  setTimeout(() => {
@@ -30909,11 +32454,11 @@ class MobileHomePageComponent {
30909
32454
  ...post,
30910
32455
  postId: post.id,
30911
32456
  avatarType: post.avatarType === 'icon' ? undefined : post.avatarType,
30912
- focusComment
32457
+ focusComment,
30913
32458
  };
30914
32459
  await this.postModal.open(postData, {
30915
32460
  currentUserName: 'Lars Mikkelsen',
30916
- currentUserInitials: this.userService.avatarInitials()
32461
+ currentUserInitials: this.userService.avatarInitials(),
30917
32462
  });
30918
32463
  }
30919
32464
  }
@@ -31001,7 +32546,7 @@ class MobileHomePageComponent {
31001
32546
  initialBreakpoint: 1,
31002
32547
  handle: true,
31003
32548
  backdropDismiss: true,
31004
- cssClass: 'auto-height'
32549
+ cssClass: 'auto-height',
31005
32550
  });
31006
32551
  const result = await sheet.onWillDismiss();
31007
32552
  if (result.role === 'select' && result.data) {
@@ -31012,7 +32557,7 @@ class MobileHomePageComponent {
31012
32557
  if (post) {
31013
32558
  this.postsService.updatePost(postId, {
31014
32559
  isLiked: !post.isLiked,
31015
- likeCount: post.isLiked ? post.likeCount - 1 : post.likeCount + 1
32560
+ likeCount: post.isLiked ? post.likeCount - 1 : post.likeCount + 1,
31016
32561
  });
31017
32562
  }
31018
32563
  break;
@@ -31032,216 +32577,189 @@ class MobileHomePageComponent {
31032
32577
  @if (!pageLoading.isCoveringScreen()) {
31033
32578
  <ds-mobile-page-main
31034
32579
  class="home-content"
32580
+ [showNotification]="true"
31035
32581
  [class.home-content--animating]="pageLoading.isRevealAnimation()"
31036
32582
  [firstEntry]="pageLoading.isRevealAnimation()"
31037
32583
  #pageComponent
31038
32584
  title="Hjem"
31039
32585
  headerTitle="Velkommen, Lars"
32586
+ [notificationCount]="notificationService.unreadCount()"
31040
32587
  [avatarInitials]="userService.avatarInitials()"
31041
32588
  [avatarType]="userService.avatarType()"
32589
+ (notificationClick)="handleNotificationClick()"
31042
32590
  (refresh)="handleRefresh($event)"
32591
+ >
32592
+ <!-- Offline indicator -->
32593
+ @if (pageComponent.isOffline()) {
32594
+ <ds-mobile-offline-banner offline-indicator title="Ingen internetforbindelse" message="Nogle funktioner kan være utilgængelige"> </ds-mobile-offline-banner>
32595
+ }
32596
+
32597
+ <!-- Property banner → beboere (prototype IA) -->
32598
+ <div
32599
+ header-content
32600
+ class="property-banner-nav"
32601
+ role="button"
32602
+ tabindex="0"
32603
+ (click)="navigateToTenants()"
32604
+ (keydown.enter)="navigateToTenants()"
32605
+ (keydown.space)="$event.preventDefault(); navigateToTenants()"
32606
+ aria-label="Se beboere på ejendommen"
31043
32607
  >
31044
-
31045
- <!-- Offline indicator -->
31046
- @if (pageComponent.isOffline()) {
31047
- <ds-mobile-offline-banner
31048
- offline-indicator
31049
- title="Ingen internetforbindelse"
31050
- message="Nogle funktioner kan være utilgængelige">
31051
- </ds-mobile-offline-banner>
31052
- }
31053
-
31054
- <!-- Property banner → beboere (prototype IA) -->
31055
- <div
31056
- header-content
31057
- class="property-banner-nav"
31058
- role="button"
31059
- tabindex="0"
31060
- (click)="navigateToTenants()"
31061
- (keydown.enter)="navigateToTenants()"
31062
- (keydown.space)="$event.preventDefault(); navigateToTenants()"
31063
- aria-label="Se beboere på ejendommen">
31064
- <ds-mobile-property-banner
31065
- address="Toftegårds Allé 5A, 2. tv."
31066
- photoUrl="/Assets/Dummy-photos/building.jpg"
31067
- [tenantCount]="24">
31068
- </ds-mobile-property-banner>
31069
- </div>
32608
+ <ds-mobile-property-banner address="Toftegårds Allé 5A, 2. tv." photoUrl="/Assets/Dummy-photos/building.jpg" [tenantCount]="24"> </ds-mobile-property-banner>
32609
+ </div>
31070
32610
 
31071
- @if (familyAccessService.acceptedInviteContext(); as invite) {
31072
- <ds-mobile-section [showBorder]="false" padding="20px 20px 0">
31073
- <div class="welcome-toast">
31074
- <div class="toast-icon">
31075
- <ds-icon name="remixUserSmileLine" size="12px" />
31076
- </div>
31077
- <div class="welcome-toast-content">
31078
- <p class="welcome-toast-heading">Du er nu familiemedlem!</p>
31079
- <p class="welcome-toast-text">
31080
- Tilknyttet <strong>{{ invite.propertyAddress }}</strong> via <strong>{{ invite.inviterName }}</strong>
31081
- </p>
32611
+ @if (familyAccessService.acceptedInviteContext(); as invite) {
32612
+ <ds-mobile-section [showBorder]="false" padding="20px 20px 0">
32613
+ <div class="welcome-toast">
32614
+ <div class="toast-icon">
32615
+ <ds-icon name="remixUserSmileLine" size="12px" />
32616
+ </div>
32617
+ <div class="welcome-toast-content">
32618
+ <p class="welcome-toast-heading">Du er nu familiemedlem!</p>
32619
+ <p class="welcome-toast-text">
32620
+ Tilknyttet <strong>{{ invite.propertyAddress }}</strong> via <strong>{{ invite.inviterName }}</strong>
32621
+ </p>
32622
+ </div>
32623
+ <button class="toast-dismiss" (click)="familyAccessService.clearAcceptedInviteContext()" aria-label="Luk">
32624
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
32625
+ <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
32626
+ </svg>
32627
+ </button>
31082
32628
  </div>
31083
- <button class="toast-dismiss" (click)="familyAccessService.clearAcceptedInviteContext()" aria-label="Luk">
31084
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
31085
- <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
31086
- </svg>
31087
- </button>
32629
+ </ds-mobile-section>
32630
+ }
32631
+
32632
+ <!-- Recent Community Posts Section (with content) -->
32633
+ <ds-mobile-section headline="Seneste opslag" linkText="Se alle" (linkClick)="navigateToCommunity()">
32634
+ <div class="posts-list">
32635
+ @for (post of recentPosts(); track post.id) {
32636
+ <ds-mobile-interactive-list-item-post
32637
+ [authorName]="post.authorName"
32638
+ [authorRole]="post.authorRole"
32639
+ [timestamp]="post.timestamp"
32640
+ [avatarType]="post.avatarType"
32641
+ [avatarSrc]="post.avatarSrc || ''"
32642
+ [avatarInitials]="post.avatarInitials || ''"
32643
+ [showBadge]="post.showBadge || false"
32644
+ [clickable]="true"
32645
+ [moreActions]="true"
32646
+ (postClick)="openPost(post.id)"
32647
+ (commentClick)="openPost(post.id, true)"
32648
+ (longPress)="handlePostLongPress(post.id)"
32649
+ >
32650
+ <post-content>
32651
+ @if (post.content) {
32652
+ <post-text>{{ post.content }}</post-text>
32653
+ }
32654
+ </post-content>
32655
+
32656
+ <post-actions>
32657
+ <action-like [count]="post.likeCount" [active]="post.isLiked" />
32658
+ <action-comment [count]="post.commentCount" (commentClick)="openPost(post.id, true)" />
32659
+ </post-actions>
32660
+ </ds-mobile-interactive-list-item-post>
32661
+ }
31088
32662
  </div>
31089
32663
  </ds-mobile-section>
31090
- }
31091
-
31092
- <!-- Recent Community Posts Section (with content) -->
31093
- <ds-mobile-section
31094
- headline="Seneste opslag"
31095
- linkText="Se alle"
31096
- (linkClick)="navigateToCommunity()">
31097
-
31098
- <div class="posts-list">
31099
- @for (post of recentPosts(); track post.id) {
31100
- <ds-mobile-interactive-list-item-post
31101
- [authorName]="post.authorName"
31102
- [authorRole]="post.authorRole"
31103
- [timestamp]="post.timestamp"
31104
- [avatarType]="post.avatarType"
31105
- [avatarSrc]="post.avatarSrc || ''"
31106
- [avatarInitials]="post.avatarInitials || ''"
31107
- [showBadge]="post.showBadge || false"
31108
- [clickable]="true"
31109
- [moreActions]="true"
31110
- (postClick)="openPost(post.id)"
31111
- (commentClick)="openPost(post.id, true)"
31112
- (longPress)="handlePostLongPress(post.id)">
31113
-
31114
- <post-content>
31115
- @if (post.content) {
31116
- <post-text>{{ post.content }}</post-text>
31117
- }
31118
- </post-content>
31119
-
31120
- <post-actions>
31121
- <action-like [count]="post.likeCount" [active]="post.isLiked" />
31122
- <action-comment [count]="post.commentCount" (commentClick)="openPost(post.id, true)" />
31123
- </post-actions>
31124
- </ds-mobile-interactive-list-item-post>
31125
- }
31126
- </div>
31127
- </ds-mobile-section>
31128
32664
 
31129
- <ds-mobile-section
31130
- headline="Seneste beskeder"
31131
- linkText="Se alle"
31132
- (linkClick)="navigateToMessages()">
31133
- <div class="messages-preview-list">
31134
- @for (conv of recentPeerMessages(); track conv.id) {
31135
- @if (isPeerGroupConversation(conv)) {
31136
- <ds-mobile-interactive-list-item-message
31137
- [senderName]="conv.title"
31138
- [senderRole]="conv.memberIds.length + ' medlemmer'"
31139
- [timestamp]="conv.timestamp"
31140
- [message]="conv.lastMessage"
31141
- [avatarInitials]="''"
31142
- [avatarType]="'initials'"
31143
- [groupStackMembers]="peerChat.resolveGroupMembers(conv)"
31144
- [groupCustomAvatarUrl]="conv.customAvatarUrl ?? ''"
31145
- groupStackExcludeParticipantId="me"
31146
- [showAvatarBadge]="false"
31147
- [unread]="conv.unread"
31148
- [clickable]="true"
31149
- (messageClick)="openPeerMessagePreview(conv)">
31150
- </ds-mobile-interactive-list-item-message>
31151
- } @else {
31152
- <ds-mobile-interactive-list-item-message
31153
- [senderName]="conv.participant.name"
31154
- [senderRole]="conv.participant.role ?? ''"
31155
- [timestamp]="conv.timestamp"
31156
- [message]="conv.lastMessage"
31157
- [avatarInitials]="conv.participant.avatarInitials ?? ''"
31158
- [avatarType]="conv.participant.avatarType ?? 'initials'"
31159
- [showAvatarBadge]="false"
31160
- [unread]="conv.unread"
32665
+ <ds-mobile-section headline="Seneste beskeder" linkText="Se alle" (linkClick)="navigateToMessages()">
32666
+ <div class="messages-preview-list">
32667
+ @for (conv of recentPeerMessages(); track conv.id) {
32668
+ @if (isPeerGroupConversation(conv)) {
32669
+ <ds-mobile-interactive-list-item-message
32670
+ [senderName]="conv.title"
32671
+ [senderRole]="conv.memberIds.length + ' medlemmer'"
32672
+ [timestamp]="conv.timestamp"
32673
+ [message]="conv.lastMessage"
32674
+ [avatarInitials]="''"
32675
+ [avatarType]="'initials'"
32676
+ [groupStackMembers]="peerChat.resolveGroupMembers(conv)"
32677
+ [groupCustomAvatarUrl]="conv.customAvatarUrl ?? ''"
32678
+ groupStackExcludeParticipantId="me"
32679
+ [showAvatarBadge]="false"
32680
+ [unread]="conv.unread"
32681
+ [clickable]="true"
32682
+ (messageClick)="openPeerMessagePreview(conv)"
32683
+ >
32684
+ </ds-mobile-interactive-list-item-message>
32685
+ } @else {
32686
+ <ds-mobile-interactive-list-item-message
32687
+ [senderName]="conv.participant.name"
32688
+ [senderRole]="conv.participant.role ?? ''"
32689
+ [timestamp]="conv.timestamp"
32690
+ [message]="conv.lastMessage"
32691
+ [avatarInitials]="conv.participant.avatarInitials ?? ''"
32692
+ [avatarType]="conv.participant.avatarType ?? 'initials'"
32693
+ [showAvatarBadge]="false"
32694
+ [unread]="conv.unread"
32695
+ [clickable]="true"
32696
+ (messageClick)="openPeerMessagePreview(conv)"
32697
+ >
32698
+ </ds-mobile-interactive-list-item-message>
32699
+ }
32700
+ }
32701
+ </div>
32702
+ </ds-mobile-section>
32703
+
32704
+ <!-- Services preview -->
32705
+ @if (previewVendors().length > 0) {
32706
+ <ds-mobile-section headline="Services" linkText="Se alle" contentGap="0px" (linkClick)="navigateToServices()">
32707
+ @for (vendor of previewVendors(); track vendor.id) {
32708
+ <ds-mobile-interactive-list-item-service
32709
+ [title]="vendor.name"
32710
+ [description]="vendor.description || ''"
32711
+ [logo]="vendor.logo || ''"
32712
+ (serviceClick)="openVendorSheet(vendor)"
32713
+ />
32714
+ }
32715
+ </ds-mobile-section>
32716
+ }
32717
+
32718
+ <!-- Recent Community Posts Section (empty state) -->
32719
+ <ds-mobile-section>
32720
+ <div class="empty-state">
32721
+ <ds-mobile-illustration variant="post" alt="No posts" />
32722
+ <h3 class="empty-state-title">Ingen opslag endnu</h3>
32723
+ <p class="empty-state-description">Der er ingen opslag i fællesområdet i øjeblikket</p>
32724
+
32725
+ <ds-button variant="secondary" trailingIcon="remixArrowRightSLine" (click)="navigateToCommunity()"> Gå til fællesområdet </ds-button>
32726
+ </div>
32727
+ </ds-mobile-section>
32728
+
32729
+ <!-- Open Inquiries Section (with content) -->
32730
+ <ds-mobile-section headline="Åbne henvendelser" linkText="Se alle" (linkClick)="navigateToInquiries()">
32731
+ <div class="inquiries-list">
32732
+ @for (inquiry of openInquiries(); track inquiry.id) {
32733
+ <ds-mobile-interactive-list-item-inquiry
32734
+ [title]="inquiry.title"
32735
+ [description]="inquiry.description"
32736
+ [status]="inquiry.status"
32737
+ [timestamp]="inquiry.timestamp"
32738
+ [iconName]="'remixTodoLine'"
31161
32739
  [clickable]="true"
31162
- (messageClick)="openPeerMessagePreview(conv)">
31163
- </ds-mobile-interactive-list-item-message>
32740
+ [showChevron]="false"
32741
+ [enableLongPress]="false"
32742
+ (inquiryClick)="openInquiryDetail(inquiry.id)"
32743
+ >
32744
+ </ds-mobile-interactive-list-item-inquiry>
31164
32745
  }
31165
- }
31166
- </div>
31167
- </ds-mobile-section>
31168
-
31169
- <!-- Services preview -->
31170
- @if (previewVendors().length > 0) {
31171
- <ds-mobile-section
31172
- headline="Services"
31173
- linkText="Se alle"
31174
- contentGap="0px"
31175
- (linkClick)="navigateToServices()">
31176
- @for (vendor of previewVendors(); track vendor.id) {
31177
- <ds-mobile-interactive-list-item-service
31178
- [title]="vendor.name"
31179
- [description]="vendor.description || ''"
31180
- [logo]="vendor.logo || ''"
31181
- (serviceClick)="openVendorSheet(vendor)"
31182
- />
31183
- }
32746
+ </div>
31184
32747
  </ds-mobile-section>
31185
- }
31186
32748
 
31187
- <!-- Recent Community Posts Section (empty state) -->
31188
- <ds-mobile-section>
31189
- <div class="empty-state">
31190
- <ds-mobile-illustration variant="post" alt="No posts" />
31191
- <h3 class="empty-state-title">Ingen opslag endnu</h3>
31192
- <p class="empty-state-description">Der er ingen opslag i fællesområdet i øjeblikket</p>
31193
-
31194
- <ds-button
31195
- variant="secondary"
31196
- trailingIcon="remixArrowRightSLine"
31197
- (click)="navigateToCommunity()">
31198
- Gå til fællesområdet
31199
- </ds-button>
31200
- </div>
31201
- </ds-mobile-section>
31202
-
31203
- <!-- Open Inquiries Section (with content) -->
31204
- <ds-mobile-section
31205
- headline="Åbne henvendelser"
31206
- linkText="Se alle"
31207
- (linkClick)="navigateToInquiries()">
31208
-
31209
- <div class="inquiries-list">
31210
- @for (inquiry of openInquiries(); track inquiry.id) {
31211
- <ds-mobile-interactive-list-item-inquiry
31212
- [title]="inquiry.title"
31213
- [description]="inquiry.description"
31214
- [status]="inquiry.status"
31215
- [timestamp]="inquiry.timestamp"
31216
- [iconName]="'remixTodoLine'"
31217
- [clickable]="true"
31218
- [showChevron]="false"
31219
- [enableLongPress]="false"
31220
- (inquiryClick)="openInquiryDetail(inquiry.id)">
31221
- </ds-mobile-interactive-list-item-inquiry>
31222
- }
31223
- </div>
31224
- </ds-mobile-section>
31225
-
31226
- <!-- Open Inquiries Section (empty state) -->
31227
- <ds-mobile-section>
31228
- <div class="empty-state">
31229
- <ds-mobile-illustration variant="inquiry" alt="No inquiries" />
31230
- <h3 class="empty-state-title">Ingen åbne henvendelser</h3>
31231
- <p class="empty-state-description">Du har ingen åbne henvendelser i øjeblikket</p>
31232
-
31233
- <ds-button
31234
- variant="secondary"
31235
- trailingIcon="remixArrowRightSLine"
31236
- (click)="navigateToInquiries()">
31237
- Gå til henvendelser
31238
- </ds-button>
31239
- </div>
31240
- </ds-mobile-section>
31241
- </ds-mobile-page-main>
31242
- } <!-- end @if (!isCoveringScreen()) -->
32749
+ <!-- Open Inquiries Section (empty state) -->
32750
+ <ds-mobile-section>
32751
+ <div class="empty-state">
32752
+ <ds-mobile-illustration variant="inquiry" alt="No inquiries" />
32753
+ <h3 class="empty-state-title">Ingen åbne henvendelser</h3>
32754
+ <p class="empty-state-description">Du har ingen åbne henvendelser i øjeblikket</p>
31243
32755
 
31244
- `, isInline: true, styles: [".posts-list,.messages-preview-list{display:flex;flex-direction:column}.property-banner-nav{display:block;width:100%;border-radius:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.property-banner-nav:focus-visible{outline:2px solid var(--color-accent, #6B5FF5);outline-offset:2px}.inquiries-list,.services-preview-list{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center}.empty-state ds-button{display:block;margin-top:16px}.empty-state ds-button::ng-deep .btn{width:100%;border-radius:9999px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}@keyframes slideDown{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}.welcome-toast{padding:10px 14px;border-radius:12px;background:var(--color-background-brand-secondary, #EEF0FF);display:flex;align-items:flex-start;gap:10px;font-size:14px;font-weight:500;color:var(--color-accent, #6B5FF5);animation:slideDown .2s ease-out}.toast-icon{width:20px;height:20px;border-radius:50%;background:var(--color-accent, #6B5FF5);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff;margin-top:1px}.welcome-toast-content{flex:1;display:flex;flex-direction:column;gap:2px}.welcome-toast-heading{font-family:Brockmann,sans-serif;font-size:14px;font-weight:600;color:var(--color-brand-content, #3B3691);margin:0}.welcome-toast-text{font-family:Brockmann,sans-serif;font-size:13px;line-height:1.4;color:var(--color-brand-content, #3B3691);margin:0;opacity:.8}.welcome-toast-text strong{font-weight:600;opacity:1}.toast-dismiss{margin-left:auto;background:none;border:none;cursor:pointer;flex-shrink:0;color:var(--color-accent, #6B5FF5);display:flex;align-items:center;justify-content:center}.home-content--animating{animation:homeReveal .3s var(--spring-curve-smooth) both}@keyframes homeReveal{0%{opacity:0;transform:translateY(128px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobilePropertyBannerComponent, selector: "ds-mobile-property-banner", inputs: ["address", "photoUrl", "tenantCount"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "align", "clickable", "enableLongPress", "moreActions"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "timestamp", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "unreadStyle", "clickable", "align", "showAvatarBadge", "groupStackMembers", "groupCustomAvatarUrl", "groupStackExcludeParticipantId"], outputs: ["messageClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemServiceComponent, selector: "ds-mobile-interactive-list-item-service", inputs: ["title", "description", "logo", "showChevron"], outputs: ["serviceClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }] });
32756
+ <ds-button variant="secondary" trailingIcon="remixArrowRightSLine" (click)="navigateToInquiries()"> til henvendelser </ds-button>
32757
+ </div>
32758
+ </ds-mobile-section>
32759
+ </ds-mobile-page-main>
32760
+ }
32761
+ <!-- end @if (!isCoveringScreen()) -->
32762
+ `, isInline: true, styles: [".posts-list,.messages-preview-list{display:flex;flex-direction:column}.property-banner-nav{display:block;width:100%;border-radius:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.property-banner-nav:focus-visible{outline:2px solid var(--color-accent, #6b5ff5);outline-offset:2px}.inquiries-list,.services-preview-list{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center}.empty-state ds-button{display:block;margin-top:16px}.empty-state ds-button::ng-deep .btn{width:100%;border-radius:9999px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}@keyframes slideDown{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}.welcome-toast{padding:10px 14px;border-radius:12px;background:var(--color-background-brand-secondary, #eef0ff);display:flex;align-items:flex-start;gap:10px;font-size:14px;font-weight:500;color:var(--color-accent, #6b5ff5);animation:slideDown .2s ease-out}.toast-icon{width:20px;height:20px;border-radius:50%;background:var(--color-accent, #6b5ff5);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff;margin-top:1px}.welcome-toast-content{flex:1;display:flex;flex-direction:column;gap:2px}.welcome-toast-heading{font-family:Brockmann,sans-serif;font-size:14px;font-weight:600;color:var(--color-brand-content, #3b3691);margin:0}.welcome-toast-text{font-family:Brockmann,sans-serif;font-size:13px;line-height:1.4;color:var(--color-brand-content, #3b3691);margin:0;opacity:.8}.welcome-toast-text strong{font-weight:600;opacity:1}.toast-dismiss{margin-left:auto;background:none;border:none;cursor:pointer;flex-shrink:0;color:var(--color-accent, #6b5ff5);display:flex;align-items:center;justify-content:center}.home-content--animating{animation:homeReveal .3s var(--spring-curve-smooth) both}@keyframes homeReveal{0%{opacity:0;transform:translateY(128px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showNotification", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobilePropertyBannerComponent, selector: "ds-mobile-property-banner", inputs: ["address", "photoUrl", "tenantCount"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "align", "clickable", "enableLongPress", "moreActions"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "timestamp", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "unreadStyle", "clickable", "align", "showAvatarBadge", "groupStackMembers", "groupCustomAvatarUrl", "groupStackExcludeParticipantId"], outputs: ["messageClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemServiceComponent, selector: "ds-mobile-interactive-list-item-service", inputs: ["title", "description", "logo", "showChevron"], outputs: ["serviceClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }] });
31245
32763
  }
31246
32764
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileHomePageComponent, decorators: [{
31247
32765
  type: Component,
@@ -31255,14 +32773,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
31255
32773
  DsMobileInteractiveListItemPostComponent,
31256
32774
  DsMobileInteractiveListItemInquiryComponent,
31257
32775
  DsMobileInteractiveListItemMessageComponent,
31258
- DsMobileInteractiveListItemBookingComponent,
31259
32776
  DsMobileInteractiveListItemServiceComponent,
31260
32777
  DsMobileOfflineBannerComponent,
31261
32778
  PostContentComponent,
31262
32779
  PostTextComponent,
31263
32780
  PostActionsComponent,
31264
32781
  ActionLikeComponent,
31265
- ActionCommentComponent
32782
+ ActionCommentComponent,
31266
32783
  ], template: `
31267
32784
  <!-- Full-screen loading state (first entry) — rendered by layout, not here -->
31268
32785
 
@@ -31272,216 +32789,189 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
31272
32789
  @if (!pageLoading.isCoveringScreen()) {
31273
32790
  <ds-mobile-page-main
31274
32791
  class="home-content"
32792
+ [showNotification]="true"
31275
32793
  [class.home-content--animating]="pageLoading.isRevealAnimation()"
31276
32794
  [firstEntry]="pageLoading.isRevealAnimation()"
31277
32795
  #pageComponent
31278
32796
  title="Hjem"
31279
32797
  headerTitle="Velkommen, Lars"
32798
+ [notificationCount]="notificationService.unreadCount()"
31280
32799
  [avatarInitials]="userService.avatarInitials()"
31281
32800
  [avatarType]="userService.avatarType()"
32801
+ (notificationClick)="handleNotificationClick()"
31282
32802
  (refresh)="handleRefresh($event)"
32803
+ >
32804
+ <!-- Offline indicator -->
32805
+ @if (pageComponent.isOffline()) {
32806
+ <ds-mobile-offline-banner offline-indicator title="Ingen internetforbindelse" message="Nogle funktioner kan være utilgængelige"> </ds-mobile-offline-banner>
32807
+ }
32808
+
32809
+ <!-- Property banner → beboere (prototype IA) -->
32810
+ <div
32811
+ header-content
32812
+ class="property-banner-nav"
32813
+ role="button"
32814
+ tabindex="0"
32815
+ (click)="navigateToTenants()"
32816
+ (keydown.enter)="navigateToTenants()"
32817
+ (keydown.space)="$event.preventDefault(); navigateToTenants()"
32818
+ aria-label="Se beboere på ejendommen"
31283
32819
  >
31284
-
31285
- <!-- Offline indicator -->
31286
- @if (pageComponent.isOffline()) {
31287
- <ds-mobile-offline-banner
31288
- offline-indicator
31289
- title="Ingen internetforbindelse"
31290
- message="Nogle funktioner kan være utilgængelige">
31291
- </ds-mobile-offline-banner>
31292
- }
31293
-
31294
- <!-- Property banner → beboere (prototype IA) -->
31295
- <div
31296
- header-content
31297
- class="property-banner-nav"
31298
- role="button"
31299
- tabindex="0"
31300
- (click)="navigateToTenants()"
31301
- (keydown.enter)="navigateToTenants()"
31302
- (keydown.space)="$event.preventDefault(); navigateToTenants()"
31303
- aria-label="Se beboere på ejendommen">
31304
- <ds-mobile-property-banner
31305
- address="Toftegårds Allé 5A, 2. tv."
31306
- photoUrl="/Assets/Dummy-photos/building.jpg"
31307
- [tenantCount]="24">
31308
- </ds-mobile-property-banner>
31309
- </div>
32820
+ <ds-mobile-property-banner address="Toftegårds Allé 5A, 2. tv." photoUrl="/Assets/Dummy-photos/building.jpg" [tenantCount]="24"> </ds-mobile-property-banner>
32821
+ </div>
31310
32822
 
31311
- @if (familyAccessService.acceptedInviteContext(); as invite) {
31312
- <ds-mobile-section [showBorder]="false" padding="20px 20px 0">
31313
- <div class="welcome-toast">
31314
- <div class="toast-icon">
31315
- <ds-icon name="remixUserSmileLine" size="12px" />
31316
- </div>
31317
- <div class="welcome-toast-content">
31318
- <p class="welcome-toast-heading">Du er nu familiemedlem!</p>
31319
- <p class="welcome-toast-text">
31320
- Tilknyttet <strong>{{ invite.propertyAddress }}</strong> via <strong>{{ invite.inviterName }}</strong>
31321
- </p>
32823
+ @if (familyAccessService.acceptedInviteContext(); as invite) {
32824
+ <ds-mobile-section [showBorder]="false" padding="20px 20px 0">
32825
+ <div class="welcome-toast">
32826
+ <div class="toast-icon">
32827
+ <ds-icon name="remixUserSmileLine" size="12px" />
32828
+ </div>
32829
+ <div class="welcome-toast-content">
32830
+ <p class="welcome-toast-heading">Du er nu familiemedlem!</p>
32831
+ <p class="welcome-toast-text">
32832
+ Tilknyttet <strong>{{ invite.propertyAddress }}</strong> via <strong>{{ invite.inviterName }}</strong>
32833
+ </p>
32834
+ </div>
32835
+ <button class="toast-dismiss" (click)="familyAccessService.clearAcceptedInviteContext()" aria-label="Luk">
32836
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
32837
+ <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
32838
+ </svg>
32839
+ </button>
31322
32840
  </div>
31323
- <button class="toast-dismiss" (click)="familyAccessService.clearAcceptedInviteContext()" aria-label="Luk">
31324
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
31325
- <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
31326
- </svg>
31327
- </button>
32841
+ </ds-mobile-section>
32842
+ }
32843
+
32844
+ <!-- Recent Community Posts Section (with content) -->
32845
+ <ds-mobile-section headline="Seneste opslag" linkText="Se alle" (linkClick)="navigateToCommunity()">
32846
+ <div class="posts-list">
32847
+ @for (post of recentPosts(); track post.id) {
32848
+ <ds-mobile-interactive-list-item-post
32849
+ [authorName]="post.authorName"
32850
+ [authorRole]="post.authorRole"
32851
+ [timestamp]="post.timestamp"
32852
+ [avatarType]="post.avatarType"
32853
+ [avatarSrc]="post.avatarSrc || ''"
32854
+ [avatarInitials]="post.avatarInitials || ''"
32855
+ [showBadge]="post.showBadge || false"
32856
+ [clickable]="true"
32857
+ [moreActions]="true"
32858
+ (postClick)="openPost(post.id)"
32859
+ (commentClick)="openPost(post.id, true)"
32860
+ (longPress)="handlePostLongPress(post.id)"
32861
+ >
32862
+ <post-content>
32863
+ @if (post.content) {
32864
+ <post-text>{{ post.content }}</post-text>
32865
+ }
32866
+ </post-content>
32867
+
32868
+ <post-actions>
32869
+ <action-like [count]="post.likeCount" [active]="post.isLiked" />
32870
+ <action-comment [count]="post.commentCount" (commentClick)="openPost(post.id, true)" />
32871
+ </post-actions>
32872
+ </ds-mobile-interactive-list-item-post>
32873
+ }
31328
32874
  </div>
31329
32875
  </ds-mobile-section>
31330
- }
31331
-
31332
- <!-- Recent Community Posts Section (with content) -->
31333
- <ds-mobile-section
31334
- headline="Seneste opslag"
31335
- linkText="Se alle"
31336
- (linkClick)="navigateToCommunity()">
31337
-
31338
- <div class="posts-list">
31339
- @for (post of recentPosts(); track post.id) {
31340
- <ds-mobile-interactive-list-item-post
31341
- [authorName]="post.authorName"
31342
- [authorRole]="post.authorRole"
31343
- [timestamp]="post.timestamp"
31344
- [avatarType]="post.avatarType"
31345
- [avatarSrc]="post.avatarSrc || ''"
31346
- [avatarInitials]="post.avatarInitials || ''"
31347
- [showBadge]="post.showBadge || false"
31348
- [clickable]="true"
31349
- [moreActions]="true"
31350
- (postClick)="openPost(post.id)"
31351
- (commentClick)="openPost(post.id, true)"
31352
- (longPress)="handlePostLongPress(post.id)">
31353
-
31354
- <post-content>
31355
- @if (post.content) {
31356
- <post-text>{{ post.content }}</post-text>
31357
- }
31358
- </post-content>
31359
-
31360
- <post-actions>
31361
- <action-like [count]="post.likeCount" [active]="post.isLiked" />
31362
- <action-comment [count]="post.commentCount" (commentClick)="openPost(post.id, true)" />
31363
- </post-actions>
31364
- </ds-mobile-interactive-list-item-post>
31365
- }
31366
- </div>
31367
- </ds-mobile-section>
31368
32876
 
31369
- <ds-mobile-section
31370
- headline="Seneste beskeder"
31371
- linkText="Se alle"
31372
- (linkClick)="navigateToMessages()">
31373
- <div class="messages-preview-list">
31374
- @for (conv of recentPeerMessages(); track conv.id) {
31375
- @if (isPeerGroupConversation(conv)) {
31376
- <ds-mobile-interactive-list-item-message
31377
- [senderName]="conv.title"
31378
- [senderRole]="conv.memberIds.length + ' medlemmer'"
31379
- [timestamp]="conv.timestamp"
31380
- [message]="conv.lastMessage"
31381
- [avatarInitials]="''"
31382
- [avatarType]="'initials'"
31383
- [groupStackMembers]="peerChat.resolveGroupMembers(conv)"
31384
- [groupCustomAvatarUrl]="conv.customAvatarUrl ?? ''"
31385
- groupStackExcludeParticipantId="me"
31386
- [showAvatarBadge]="false"
31387
- [unread]="conv.unread"
31388
- [clickable]="true"
31389
- (messageClick)="openPeerMessagePreview(conv)">
31390
- </ds-mobile-interactive-list-item-message>
31391
- } @else {
31392
- <ds-mobile-interactive-list-item-message
31393
- [senderName]="conv.participant.name"
31394
- [senderRole]="conv.participant.role ?? ''"
31395
- [timestamp]="conv.timestamp"
31396
- [message]="conv.lastMessage"
31397
- [avatarInitials]="conv.participant.avatarInitials ?? ''"
31398
- [avatarType]="conv.participant.avatarType ?? 'initials'"
31399
- [showAvatarBadge]="false"
31400
- [unread]="conv.unread"
32877
+ <ds-mobile-section headline="Seneste beskeder" linkText="Se alle" (linkClick)="navigateToMessages()">
32878
+ <div class="messages-preview-list">
32879
+ @for (conv of recentPeerMessages(); track conv.id) {
32880
+ @if (isPeerGroupConversation(conv)) {
32881
+ <ds-mobile-interactive-list-item-message
32882
+ [senderName]="conv.title"
32883
+ [senderRole]="conv.memberIds.length + ' medlemmer'"
32884
+ [timestamp]="conv.timestamp"
32885
+ [message]="conv.lastMessage"
32886
+ [avatarInitials]="''"
32887
+ [avatarType]="'initials'"
32888
+ [groupStackMembers]="peerChat.resolveGroupMembers(conv)"
32889
+ [groupCustomAvatarUrl]="conv.customAvatarUrl ?? ''"
32890
+ groupStackExcludeParticipantId="me"
32891
+ [showAvatarBadge]="false"
32892
+ [unread]="conv.unread"
32893
+ [clickable]="true"
32894
+ (messageClick)="openPeerMessagePreview(conv)"
32895
+ >
32896
+ </ds-mobile-interactive-list-item-message>
32897
+ } @else {
32898
+ <ds-mobile-interactive-list-item-message
32899
+ [senderName]="conv.participant.name"
32900
+ [senderRole]="conv.participant.role ?? ''"
32901
+ [timestamp]="conv.timestamp"
32902
+ [message]="conv.lastMessage"
32903
+ [avatarInitials]="conv.participant.avatarInitials ?? ''"
32904
+ [avatarType]="conv.participant.avatarType ?? 'initials'"
32905
+ [showAvatarBadge]="false"
32906
+ [unread]="conv.unread"
32907
+ [clickable]="true"
32908
+ (messageClick)="openPeerMessagePreview(conv)"
32909
+ >
32910
+ </ds-mobile-interactive-list-item-message>
32911
+ }
32912
+ }
32913
+ </div>
32914
+ </ds-mobile-section>
32915
+
32916
+ <!-- Services preview -->
32917
+ @if (previewVendors().length > 0) {
32918
+ <ds-mobile-section headline="Services" linkText="Se alle" contentGap="0px" (linkClick)="navigateToServices()">
32919
+ @for (vendor of previewVendors(); track vendor.id) {
32920
+ <ds-mobile-interactive-list-item-service
32921
+ [title]="vendor.name"
32922
+ [description]="vendor.description || ''"
32923
+ [logo]="vendor.logo || ''"
32924
+ (serviceClick)="openVendorSheet(vendor)"
32925
+ />
32926
+ }
32927
+ </ds-mobile-section>
32928
+ }
32929
+
32930
+ <!-- Recent Community Posts Section (empty state) -->
32931
+ <ds-mobile-section>
32932
+ <div class="empty-state">
32933
+ <ds-mobile-illustration variant="post" alt="No posts" />
32934
+ <h3 class="empty-state-title">Ingen opslag endnu</h3>
32935
+ <p class="empty-state-description">Der er ingen opslag i fællesområdet i øjeblikket</p>
32936
+
32937
+ <ds-button variant="secondary" trailingIcon="remixArrowRightSLine" (click)="navigateToCommunity()"> Gå til fællesområdet </ds-button>
32938
+ </div>
32939
+ </ds-mobile-section>
32940
+
32941
+ <!-- Open Inquiries Section (with content) -->
32942
+ <ds-mobile-section headline="Åbne henvendelser" linkText="Se alle" (linkClick)="navigateToInquiries()">
32943
+ <div class="inquiries-list">
32944
+ @for (inquiry of openInquiries(); track inquiry.id) {
32945
+ <ds-mobile-interactive-list-item-inquiry
32946
+ [title]="inquiry.title"
32947
+ [description]="inquiry.description"
32948
+ [status]="inquiry.status"
32949
+ [timestamp]="inquiry.timestamp"
32950
+ [iconName]="'remixTodoLine'"
31401
32951
  [clickable]="true"
31402
- (messageClick)="openPeerMessagePreview(conv)">
31403
- </ds-mobile-interactive-list-item-message>
32952
+ [showChevron]="false"
32953
+ [enableLongPress]="false"
32954
+ (inquiryClick)="openInquiryDetail(inquiry.id)"
32955
+ >
32956
+ </ds-mobile-interactive-list-item-inquiry>
31404
32957
  }
31405
- }
31406
- </div>
31407
- </ds-mobile-section>
31408
-
31409
- <!-- Services preview -->
31410
- @if (previewVendors().length > 0) {
31411
- <ds-mobile-section
31412
- headline="Services"
31413
- linkText="Se alle"
31414
- contentGap="0px"
31415
- (linkClick)="navigateToServices()">
31416
- @for (vendor of previewVendors(); track vendor.id) {
31417
- <ds-mobile-interactive-list-item-service
31418
- [title]="vendor.name"
31419
- [description]="vendor.description || ''"
31420
- [logo]="vendor.logo || ''"
31421
- (serviceClick)="openVendorSheet(vendor)"
31422
- />
31423
- }
32958
+ </div>
31424
32959
  </ds-mobile-section>
31425
- }
31426
32960
 
31427
- <!-- Recent Community Posts Section (empty state) -->
31428
- <ds-mobile-section>
31429
- <div class="empty-state">
31430
- <ds-mobile-illustration variant="post" alt="No posts" />
31431
- <h3 class="empty-state-title">Ingen opslag endnu</h3>
31432
- <p class="empty-state-description">Der er ingen opslag i fællesområdet i øjeblikket</p>
31433
-
31434
- <ds-button
31435
- variant="secondary"
31436
- trailingIcon="remixArrowRightSLine"
31437
- (click)="navigateToCommunity()">
31438
- Gå til fællesområdet
31439
- </ds-button>
31440
- </div>
31441
- </ds-mobile-section>
31442
-
31443
- <!-- Open Inquiries Section (with content) -->
31444
- <ds-mobile-section
31445
- headline="Åbne henvendelser"
31446
- linkText="Se alle"
31447
- (linkClick)="navigateToInquiries()">
31448
-
31449
- <div class="inquiries-list">
31450
- @for (inquiry of openInquiries(); track inquiry.id) {
31451
- <ds-mobile-interactive-list-item-inquiry
31452
- [title]="inquiry.title"
31453
- [description]="inquiry.description"
31454
- [status]="inquiry.status"
31455
- [timestamp]="inquiry.timestamp"
31456
- [iconName]="'remixTodoLine'"
31457
- [clickable]="true"
31458
- [showChevron]="false"
31459
- [enableLongPress]="false"
31460
- (inquiryClick)="openInquiryDetail(inquiry.id)">
31461
- </ds-mobile-interactive-list-item-inquiry>
31462
- }
31463
- </div>
31464
- </ds-mobile-section>
31465
-
31466
- <!-- Open Inquiries Section (empty state) -->
31467
- <ds-mobile-section>
31468
- <div class="empty-state">
31469
- <ds-mobile-illustration variant="inquiry" alt="No inquiries" />
31470
- <h3 class="empty-state-title">Ingen åbne henvendelser</h3>
31471
- <p class="empty-state-description">Du har ingen åbne henvendelser i øjeblikket</p>
31472
-
31473
- <ds-button
31474
- variant="secondary"
31475
- trailingIcon="remixArrowRightSLine"
31476
- (click)="navigateToInquiries()">
31477
- Gå til henvendelser
31478
- </ds-button>
31479
- </div>
31480
- </ds-mobile-section>
31481
- </ds-mobile-page-main>
31482
- } <!-- end @if (!isCoveringScreen()) -->
32961
+ <!-- Open Inquiries Section (empty state) -->
32962
+ <ds-mobile-section>
32963
+ <div class="empty-state">
32964
+ <ds-mobile-illustration variant="inquiry" alt="No inquiries" />
32965
+ <h3 class="empty-state-title">Ingen åbne henvendelser</h3>
32966
+ <p class="empty-state-description">Du har ingen åbne henvendelser i øjeblikket</p>
31483
32967
 
31484
- `, styles: [".posts-list,.messages-preview-list{display:flex;flex-direction:column}.property-banner-nav{display:block;width:100%;border-radius:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.property-banner-nav:focus-visible{outline:2px solid var(--color-accent, #6B5FF5);outline-offset:2px}.inquiries-list,.services-preview-list{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center}.empty-state ds-button{display:block;margin-top:16px}.empty-state ds-button::ng-deep .btn{width:100%;border-radius:9999px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}@keyframes slideDown{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}.welcome-toast{padding:10px 14px;border-radius:12px;background:var(--color-background-brand-secondary, #EEF0FF);display:flex;align-items:flex-start;gap:10px;font-size:14px;font-weight:500;color:var(--color-accent, #6B5FF5);animation:slideDown .2s ease-out}.toast-icon{width:20px;height:20px;border-radius:50%;background:var(--color-accent, #6B5FF5);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff;margin-top:1px}.welcome-toast-content{flex:1;display:flex;flex-direction:column;gap:2px}.welcome-toast-heading{font-family:Brockmann,sans-serif;font-size:14px;font-weight:600;color:var(--color-brand-content, #3B3691);margin:0}.welcome-toast-text{font-family:Brockmann,sans-serif;font-size:13px;line-height:1.4;color:var(--color-brand-content, #3B3691);margin:0;opacity:.8}.welcome-toast-text strong{font-weight:600;opacity:1}.toast-dismiss{margin-left:auto;background:none;border:none;cursor:pointer;flex-shrink:0;color:var(--color-accent, #6B5FF5);display:flex;align-items:center;justify-content:center}.home-content--animating{animation:homeReveal .3s var(--spring-curve-smooth) both}@keyframes homeReveal{0%{opacity:0;transform:translateY(128px)}to{opacity:1;transform:translateY(0)}}\n"] }]
32968
+ <ds-button variant="secondary" trailingIcon="remixArrowRightSLine" (click)="navigateToInquiries()"> til henvendelser </ds-button>
32969
+ </div>
32970
+ </ds-mobile-section>
32971
+ </ds-mobile-page-main>
32972
+ }
32973
+ <!-- end @if (!isCoveringScreen()) -->
32974
+ `, styles: [".posts-list,.messages-preview-list{display:flex;flex-direction:column}.property-banner-nav{display:block;width:100%;border-radius:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.property-banner-nav:focus-visible{outline:2px solid var(--color-accent, #6b5ff5);outline-offset:2px}.inquiries-list,.services-preview-list{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center}.empty-state ds-button{display:block;margin-top:16px}.empty-state ds-button::ng-deep .btn{width:100%;border-radius:9999px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}@keyframes slideDown{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}.welcome-toast{padding:10px 14px;border-radius:12px;background:var(--color-background-brand-secondary, #eef0ff);display:flex;align-items:flex-start;gap:10px;font-size:14px;font-weight:500;color:var(--color-accent, #6b5ff5);animation:slideDown .2s ease-out}.toast-icon{width:20px;height:20px;border-radius:50%;background:var(--color-accent, #6b5ff5);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff;margin-top:1px}.welcome-toast-content{flex:1;display:flex;flex-direction:column;gap:2px}.welcome-toast-heading{font-family:Brockmann,sans-serif;font-size:14px;font-weight:600;color:var(--color-brand-content, #3b3691);margin:0}.welcome-toast-text{font-family:Brockmann,sans-serif;font-size:13px;line-height:1.4;color:var(--color-brand-content, #3b3691);margin:0;opacity:.8}.welcome-toast-text strong{font-weight:600;opacity:1}.toast-dismiss{margin-left:auto;background:none;border:none;cursor:pointer;flex-shrink:0;color:var(--color-accent, #6b5ff5);display:flex;align-items:center;justify-content:center}.home-content--animating{animation:homeReveal .3s var(--spring-curve-smooth) both}@keyframes homeReveal{0%{opacity:0;transform:translateY(128px)}to{opacity:1;transform:translateY(0)}}\n"] }]
31485
32975
  }], ctorParameters: () => [{ type: i1$3.Router }, { type: i1.NavController }, { type: UserService }, { type: PostsService }, { type: DsMobilePostDetailModalService }, { type: TrackingPermissionService }, { type: DsMobileBottomSheetService }, { type: FamilyAccessService }, { type: PeerMessagingService }, { type: PeerChatLauncherService }, { type: DsMobileLightboxService }], propDecorators: { pageComponent: [{
31486
32976
  type: ViewChild,
31487
32977
  args: ['pageComponent']
@@ -31493,6 +32983,8 @@ class MobileInquiriesPageComponent {
31493
32983
  newInquiryModal;
31494
32984
  pageComponent;
31495
32985
  inquiriesService = inject(InquiriesService);
32986
+ notificationModal = inject(DsMobileNotificationModalService);
32987
+ notificationService = inject(NotificationService);
31496
32988
  constructor(userService, navCtrl, newInquiryModal) {
31497
32989
  this.userService = userService;
31498
32990
  this.navCtrl = navCtrl;
@@ -31528,6 +33020,11 @@ class MobileInquiriesPageComponent {
31528
33020
  console.log('Showing actions for inquiry:', inquiryId);
31529
33021
  // Show bottom sheet with actions (edit, delete, etc.)
31530
33022
  }
33023
+ async handleNotificationClick() {
33024
+ const tapped = await this.notificationModal.open();
33025
+ if (tapped)
33026
+ console.log('Notification tapped:', tapped.type, tapped.id);
33027
+ }
31531
33028
  handleRefresh(event) {
31532
33029
  console.log('Pull-to-refresh triggered on inquiries page');
31533
33030
  // Check if offline and complete immediately
@@ -31567,8 +33064,10 @@ class MobileInquiriesPageComponent {
31567
33064
  <ds-mobile-page-main
31568
33065
  #pageComponent
31569
33066
  title="Henvendelser"
33067
+ [notificationCount]="notificationService.unreadCount()"
31570
33068
  [avatarInitials]="userService.avatarInitials()"
31571
33069
  [avatarType]="userService.avatarType()"
33070
+ (notificationClick)="handleNotificationClick()"
31572
33071
  (refresh)="handleRefresh($event)">
31573
33072
 
31574
33073
  <!-- Offline indicator -->
@@ -31632,7 +33131,7 @@ class MobileInquiriesPageComponent {
31632
33131
  ariaLabel="Create new inquiry"
31633
33132
  (clicked)="createNewInquiry()">
31634
33133
  </ds-mobile-fab>
31635
- `, isInline: true, styles: [".inquiry-list-wrapper{display:flex;flex-direction:column;margin-top:-12px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsMobileInlineTabsComponent, selector: "ds-mobile-inline-tabs", inputs: ["tabs", "activeTab"], outputs: ["tabChange"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }] });
33134
+ `, isInline: true, styles: [".inquiry-list-wrapper{display:flex;flex-direction:column;margin-top:-12px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showNotification", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsMobileInlineTabsComponent, selector: "ds-mobile-inline-tabs", inputs: ["tabs", "activeTab"], outputs: ["tabChange"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }] });
31636
33135
  }
31637
33136
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileInquiriesPageComponent, decorators: [{
31638
33137
  type: Component,
@@ -31650,8 +33149,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
31650
33149
  <ds-mobile-page-main
31651
33150
  #pageComponent
31652
33151
  title="Henvendelser"
33152
+ [notificationCount]="notificationService.unreadCount()"
31653
33153
  [avatarInitials]="userService.avatarInitials()"
31654
33154
  [avatarType]="userService.avatarType()"
33155
+ (notificationClick)="handleNotificationClick()"
31655
33156
  (refresh)="handleRefresh($event)">
31656
33157
 
31657
33158
  <!-- Offline indicator -->
@@ -32323,6 +33824,87 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
32323
33824
  `, styles: [".activity-list{display:flex;flex-direction:column;gap:12px;position:relative}.activity-list ds-mobile-list-item:not(:last-child) [content-leading]:after{content:\"\";position:absolute;top:40px;left:50%;transform:translate(-50%);width:1px;height:calc(100% - 2px);background:var(--border-color-default, #e5e5e5);z-index:0}.activity-icon-wrapper{width:100%;height:100%;border-radius:8px;background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;z-index:1}.avatar-wrapper{position:relative;display:flex;align-items:start;justify-content:center;flex-shrink:0;width:100%;height:100%;z-index:1}.avatar-badge{position:absolute;bottom:-6px;right:-6px;width:20px;height:20px;border-radius:8px;background:var(--color-brand-secondary, #5d5fef);display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge svg{width:10px;position:relative;top:1px;fill:#fff}.activity-content{display:flex;flex-direction:column;gap:4px}.activity-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0}.activity-title .actor-name{font-weight:600;color:var(--text-color-default-primary, #202227)}.activity-title .activity-text{color:var(--text-color-default-secondary, #545B66);font-weight:400}.activity-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0}.activity-timestamp{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);display:flex;align-items:center;gap:4px;margin-top:2px}.detail-label{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373)}.detail-value{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:24px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227)}.detail-tag{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;background:var(--color-background-neutral-secondary, #f5f5f5);font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--text-color-default-secondary, #525866);margin-top:4px;margin-bottom:10px;width:-moz-fit-content;width:fit-content}.photo-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:8px}.photo-add{width:100%;aspect-ratio:1;border-radius:12px;border:1px dashed var(--border-color-default, #e5e5e5);background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;cursor:pointer}.photo-item{width:100%;aspect-ratio:1;border-radius:12px;-o-object-fit:cover;object-fit:cover}.empty-messages{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;gap:12px}.empty-messages-image{width:96px;height:96px;-o-object-fit:contain;object-fit:contain}.empty-messages-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #71727a);margin:0}\n"] }]
32324
33825
  }], ctorParameters: () => [{ type: UserService }, { type: DsMobileLightboxService }, { type: DsMobileChatModalService }] });
32325
33826
 
33827
+ const STORAGE_KEY = 'ds-notification-prompt-dismissed';
33828
+ const COOLDOWN_MS = 24 * 60 * 60 * 1000; // 24 hours
33829
+ /**
33830
+ * Manages the notification permission prompt lifecycle.
33831
+ *
33832
+ * - Stores the user's "Not now" dismissal timestamp in localStorage
33833
+ * - Re-shows the prompt after a 24-hour cooldown
33834
+ * - Skips the prompt entirely if native permissions are already granted
33835
+ *
33836
+ * Downstream integration: call `checkPermissions()` before checking
33837
+ * `shouldShowPrompt()` to incorporate native permission status.
33838
+ */
33839
+ class NotificationPromptService {
33840
+ permissionGranted = false;
33841
+ /**
33842
+ * Record that the user dismissed the prompt. Stores the current
33843
+ * timestamp so the prompt can be suppressed for 24 hours.
33844
+ */
33845
+ dismiss() {
33846
+ try {
33847
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({ dismissedAt: Date.now() }));
33848
+ }
33849
+ catch {
33850
+ // localStorage unavailable (SSR, private browsing quota)
33851
+ }
33852
+ }
33853
+ /**
33854
+ * Whether the prompt should be displayed right now.
33855
+ * Returns false if:
33856
+ * - Native permission is already granted
33857
+ * - The user dismissed less than 24 hours ago
33858
+ */
33859
+ shouldShowPrompt() {
33860
+ if (this.permissionGranted)
33861
+ return false;
33862
+ try {
33863
+ const raw = localStorage.getItem(STORAGE_KEY);
33864
+ if (!raw)
33865
+ return true;
33866
+ const { dismissedAt } = JSON.parse(raw);
33867
+ return Date.now() - dismissedAt >= COOLDOWN_MS;
33868
+ }
33869
+ catch {
33870
+ return true;
33871
+ }
33872
+ }
33873
+ /**
33874
+ * Call this on app launch to check native notification permission status.
33875
+ * If already granted, the prompt will never be shown.
33876
+ */
33877
+ async checkPermissions() {
33878
+ try {
33879
+ const { PushNotifications } = await import('@capacitor/push-notifications');
33880
+ const result = await PushNotifications.checkPermissions();
33881
+ this.permissionGranted = result.receive === 'granted';
33882
+ }
33883
+ catch {
33884
+ // Not on native — leave as false so the prompt can still show
33885
+ }
33886
+ }
33887
+ /**
33888
+ * Mark that the user has granted permission (called after a successful
33889
+ * allow flow so the prompt is permanently hidden for this session).
33890
+ */
33891
+ markGranted() {
33892
+ this.permissionGranted = true;
33893
+ try {
33894
+ localStorage.removeItem(STORAGE_KEY);
33895
+ }
33896
+ catch {
33897
+ // noop
33898
+ }
33899
+ }
33900
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPromptService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
33901
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPromptService, providedIn: 'root' });
33902
+ }
33903
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPromptService, decorators: [{
33904
+ type: Injectable,
33905
+ args: [{ providedIn: 'root' }]
33906
+ }] });
33907
+
32326
33908
  /**
32327
33909
  * Whitelabel Demo Modal Component
32328
33910
  *
@@ -33462,7 +35044,11 @@ class MobileTabsExampleComponent {
33462
35044
  navCtrl;
33463
35045
  whitelabelDemoModal = inject(WhitelabelDemoModalService);
33464
35046
  trackingPermissionService = inject(TrackingPermissionService);
35047
+ notificationModal = inject(DsMobileNotificationModalService);
35048
+ notificationService = inject(NotificationService);
35049
+ notificationPrompt = inject(NotificationPromptService);
33465
35050
  pageLoading = inject(PageLoadingService);
35051
+ showNotificationPrompt = signal(false, ...(ngDevMode ? [{ debugName: "showNotificationPrompt" }] : []));
33466
35052
  trackedProfileMenuItems = computed(() => {
33467
35053
  const accountActions = [
33468
35054
  {
@@ -33473,8 +35059,8 @@ class MobileTabsExampleComponent {
33473
35059
  },
33474
35060
  {
33475
35061
  action: 'settings',
33476
- title: 'Indstillinger',
33477
- icon: 'remixSettings3Line',
35062
+ title: 'Notifikationer',
35063
+ icon: 'remixNotificationLine',
33478
35064
  destructive: false,
33479
35065
  },
33480
35066
  {
@@ -33542,15 +35128,15 @@ class MobileTabsExampleComponent {
33542
35128
  this.userService.setProfileMenuItems(this.trackedProfileMenuItems());
33543
35129
  });
33544
35130
  }
33545
- ngOnInit() {
35131
+ async ngOnInit() {
33546
35132
  console.log('MobileTabsExampleComponent ngOnInit');
33547
- // Configure user avatar globally - this is now the single source of truth
33548
35133
  this.userService.setDisplayName('Lucas Møller');
33549
35134
  this.userService.setAddress('Toftegårds Allé 5A, 3. tv.');
33550
35135
  this.userService.setAvatarInitials('LM');
33551
35136
  this.userService.setAvatarType('initials');
33552
- // Initial status refresh ensures menu reflects past ATT choice.
33553
35137
  void this.trackingPermissionService.refreshTrackingStatus();
35138
+ await this.notificationPrompt.checkPermissions();
35139
+ this.showNotificationPrompt.set(this.notificationPrompt.shouldShowPrompt());
33554
35140
  }
33555
35141
  tabs = [
33556
35142
  {
@@ -33657,6 +35243,34 @@ class MobileTabsExampleComponent {
33657
35243
  });
33658
35244
  }
33659
35245
  }
35246
+ async handleNotificationAllow() {
35247
+ console.log('Notification permission: requesting native dialog...');
35248
+ this.showNotificationPrompt.set(false);
35249
+ try {
35250
+ const { PushNotifications } = await import('@capacitor/push-notifications');
35251
+ const permission = await PushNotifications.requestPermissions();
35252
+ console.log('Push permission result:', permission.receive);
35253
+ if (permission.receive === 'granted') {
35254
+ await PushNotifications.register();
35255
+ this.notificationPrompt.markGranted();
35256
+ console.log('Push notifications registered');
35257
+ }
35258
+ }
35259
+ catch (err) {
35260
+ console.warn('Push notifications not available (web/simulator):', err);
35261
+ }
35262
+ }
35263
+ handleNotificationDismiss() {
35264
+ console.log('Notification permission: dismissed (24hr cooldown)');
35265
+ this.notificationPrompt.dismiss();
35266
+ this.showNotificationPrompt.set(false);
35267
+ }
35268
+ async handleNotificationClick() {
35269
+ const tapped = await this.notificationModal.open();
35270
+ if (tapped) {
35271
+ console.log('Notification tapped:', tapped.type, tapped.id);
35272
+ }
35273
+ }
33660
35274
  /** Called by the error overlay retry button — clears error and re-navigates to home */
33661
35275
  handleRetry() {
33662
35276
  this.pageLoading.hasError.set(false);
@@ -33678,26 +35292,35 @@ class MobileTabsExampleComponent {
33678
35292
  </div>
33679
35293
  }
33680
35294
 
35295
+ <!-- Notification permission prompt (session-only, reappears on next launch) -->
35296
+ @if (showNotificationPrompt() && !pageLoading.isCoveringScreen()) {
35297
+ <ds-mobile-notification-prompt
35298
+ (allow)="handleNotificationAllow()"
35299
+ (dismiss)="handleNotificationDismiss()" />
35300
+ }
35301
+
33681
35302
  <ion-tabs class="ds-tabs-wrapper">
33682
35303
  <!-- Tab bar is hidden during the loading state -->
33683
35304
  @if (!pageLoading.isCoveringScreen()) {
33684
35305
  <ds-mobile-tab-bar
33685
35306
  [tabs]="tabs"
33686
35307
  [moreMenuItems]="moreMenuItems"
35308
+ [notificationCount]="notificationService.unreadCount()"
33687
35309
  [avatarInitials]="userService.avatarInitials()"
33688
35310
  [avatarType]="userService.avatarType()"
33689
35311
  [profileMenuItems]="profileMenuItems"
35312
+ (notificationClick)="handleNotificationClick()"
33690
35313
  (profileActionSelected)="handleProfileAction($event)"
33691
35314
  (moreMenuItemSelected)="handleMoreMenuAction($event)"
33692
35315
  >
33693
35316
  </ds-mobile-tab-bar>
33694
35317
  }
33695
35318
  </ion-tabs>
33696
- `, isInline: true, styles: [":host{display:block;height:100dvh;width:100vw;position:relative}.app-error{position:fixed;inset:0;z-index:9999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:40px 32px;background:var(--color-header-surface, #2D2356)}.app-error__title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--color-header-content, #fff);text-align:center;margin:0}.app-error__description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:#ffffffa6;text-align:center;margin:0 0 8px}.app-error ds-button::ng-deep .btn{border-radius:9999px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTabs, selector: "ion-tabs" }, { kind: "component", type: DsMobileTabBarComponent, selector: "ds-mobile-tab-bar", inputs: ["tabs", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "profileMenuItems", "moreMenuItems", "moreMenuLabel", "moreMenuIcon", "moreMenuIconActive"], outputs: ["avatarClick", "profileActionSelected", "moreMenuItemSelected"] }, { kind: "component", type: DsMobileAppLoadingComponent, selector: "ds-mobile-app-loading", inputs: ["isExiting"], outputs: ["exitComplete"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }] });
35319
+ `, isInline: true, styles: [":host{display:block;height:100dvh;width:100vw;position:relative}.app-error{position:fixed;inset:0;z-index:9999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:40px 32px;background:var(--color-header-surface, #2D2356)}.app-error__title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--color-header-content, #fff);text-align:center;margin:0}.app-error__description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:#ffffffa6;text-align:center;margin:0 0 8px}.app-error ds-button::ng-deep .btn{border-radius:9999px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTabs, selector: "ion-tabs" }, { kind: "component", type: DsMobileTabBarComponent, selector: "ds-mobile-tab-bar", inputs: ["tabs", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "profileMenuItems", "notificationCount", "moreMenuItems", "moreMenuLabel", "moreMenuIcon", "moreMenuIconActive"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "moreMenuItemSelected"] }, { kind: "component", type: DsMobileAppLoadingComponent, selector: "ds-mobile-app-loading", inputs: ["isExiting"], outputs: ["exitComplete"] }, { kind: "component", type: DsMobileNotificationPromptComponent, selector: "ds-mobile-notification-prompt", inputs: ["heading", "subtitle", "allowLabel", "dismissLabel"], outputs: ["allow", "dismiss"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }] });
33697
35320
  }
33698
35321
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileTabsExampleComponent, decorators: [{
33699
35322
  type: Component,
33700
- args: [{ selector: 'app-mobile-tabs-example', standalone: true, imports: [CommonModule, IonTabs, DsMobileTabBarComponent, DsMobileAppLoadingComponent, DsButtonComponent], template: `
35323
+ args: [{ selector: 'app-mobile-tabs-example', standalone: true, imports: [CommonModule, IonTabs, DsMobileTabBarComponent, DsMobileAppLoadingComponent, DsMobileNotificationPromptComponent, DsButtonComponent], template: `
33701
35324
  <!-- Full-screen loading screen rendered at layout level so it covers the tab bar.
33702
35325
  Kept in DOM during exiting so the exit animation can play. -->
33703
35326
  @if (pageLoading.isLoading() || pageLoading.isExiting()) {
@@ -33712,15 +35335,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
33712
35335
  </div>
33713
35336
  }
33714
35337
 
35338
+ <!-- Notification permission prompt (session-only, reappears on next launch) -->
35339
+ @if (showNotificationPrompt() && !pageLoading.isCoveringScreen()) {
35340
+ <ds-mobile-notification-prompt
35341
+ (allow)="handleNotificationAllow()"
35342
+ (dismiss)="handleNotificationDismiss()" />
35343
+ }
35344
+
33715
35345
  <ion-tabs class="ds-tabs-wrapper">
33716
35346
  <!-- Tab bar is hidden during the loading state -->
33717
35347
  @if (!pageLoading.isCoveringScreen()) {
33718
35348
  <ds-mobile-tab-bar
33719
35349
  [tabs]="tabs"
33720
35350
  [moreMenuItems]="moreMenuItems"
35351
+ [notificationCount]="notificationService.unreadCount()"
33721
35352
  [avatarInitials]="userService.avatarInitials()"
33722
35353
  [avatarType]="userService.avatarType()"
33723
35354
  [profileMenuItems]="profileMenuItems"
35355
+ (notificationClick)="handleNotificationClick()"
33724
35356
  (profileActionSelected)="handleProfileAction($event)"
33725
35357
  (moreMenuItemSelected)="handleMoreMenuAction($event)"
33726
35358
  >
@@ -33730,393 +35362,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
33730
35362
  `, styles: [":host{display:block;height:100dvh;width:100vw;position:relative}.app-error{position:fixed;inset:0;z-index:9999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:40px 32px;background:var(--color-header-surface, #2D2356)}.app-error__title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--color-header-content, #fff);text-align:center;margin:0}.app-error__description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:#ffffffa6;text-align:center;margin:0 0 8px}.app-error ds-button::ng-deep .btn{border-radius:9999px}\n"] }]
33731
35363
  }], ctorParameters: () => [{ type: UserService }, { type: i1$3.Router }, { type: i1.NavController }] });
33732
35364
 
33733
- class DsMobileBookingDetailSheetComponent {
33734
- modalController;
33735
- /** `sheet` = bottom sheet. `modal` = `ds-modal-base` shell (active + history bookings). */
33736
- presentation = 'sheet';
33737
- data;
33738
- /** When true the modal sizes to its content instead of filling the screen. */
33739
- autoHeight = false;
33740
- get isModalPresentation() {
33741
- return this.presentation === 'modal';
33742
- }
33743
- constructor(modalController) {
33744
- this.modalController = modalController;
33745
- }
33746
- close() {
33747
- this.modalController.dismiss(null, 'backdrop');
33748
- }
33749
- cancelBooking() {
33750
- this.modalController.dismiss(null, 'cancel');
33751
- }
33752
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component });
33753
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileBookingDetailSheetComponent, isStandalone: true, selector: "ds-mobile-booking-detail-sheet", inputs: { presentation: "presentation", data: "data", autoHeight: "autoHeight" }, host: { properties: { "class.presentation-modal": "this.isModalPresentation" } }, ngImport: i0, template: `
33754
- @if (presentation === 'modal') {
33755
- <ds-mobile-modal-base
33756
- [headerTitle]="data.facilityTitle"
33757
- [closeButtonLabel]="'Luk'"
33758
- [isAutoHeight]="autoHeight"
33759
- [keyboardContentBehavior]="'follow'">
33760
-
33761
- <ds-mobile-section
33762
- [showBorder]="false"
33763
- padding="20px 20px 0 20px">
33764
- <div class="hero-image-container">
33765
- @if (data.heroImage) {
33766
- <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
33767
- } @else {
33768
- <div class="hero-image-placeholder">
33769
- <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
33770
- </div>
33771
- }
33772
- </div>
33773
- </ds-mobile-section>
33774
-
33775
- <ds-mobile-section padding="20px 20px 20px 20px">
33776
- <div class="booking-summary-card">
33777
- <div class="details-section">
33778
- <h3 class="details-heading">Booking detaljer</h3>
33779
- <div class="info-rows">
33780
- @if (data.bookingDate) {
33781
- <div class="info-row">
33782
- <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
33783
- <span>{{ data.bookingDate }}</span>
33784
- </div>
33785
- }
33786
- @if (data.bookingTime) {
33787
- <div class="info-row">
33788
- <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
33789
- <span>{{ data.bookingTime }}</span>
33790
- </div>
33791
- }
33792
- @if (data.price) {
33793
- <div class="info-row">
33794
- <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
33795
- <span>{{ data.price }}</span>
33796
- </div>
33797
- }
33798
- @if (data.bookingType) {
33799
- <div class="info-row">
33800
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
33801
- <span>{{ data.bookingType }}</span>
33802
- </div>
33803
- }
33804
- @for (req of data.requirements || []; track req) {
33805
- <div class="info-row">
33806
- <ds-icon name="remixLockLine" size="16px" color="tertiary" />
33807
- <span>{{ req }}</span>
33808
- </div>
33809
- }
33810
- </div>
33811
- </div>
33812
- </div>
33813
- </ds-mobile-section>
33814
-
33815
- @if (data.canCancel) {
33816
- <div class="booking-action">
33817
- <div class="booking-actions-row">
33818
- <ds-button
33819
- class="cancel-primary"
33820
- size="md"
33821
- variant="secondary"
33822
- (clicked)="cancelBooking()">
33823
- Annuller booking
33824
- </ds-button>
33825
- </div>
33826
- </div>
33827
- }
33828
- </ds-mobile-modal-base>
33829
- } @else {
33830
- <ds-mobile-bottom-sheet-wrapper [showDragHandle]="true">
33831
- <div class="detail-header">
33832
- <h2 class="detail-title">{{ data.facilityTitle }}</h2>
33833
- <ds-icon-button
33834
- icon="remixCloseLine"
33835
- variant="ghost"
33836
- size="sm"
33837
- (clicked)="close()"
33838
- aria-label="Luk"
33839
- />
33840
- </div>
33841
-
33842
- <div class="detail-content">
33843
- <div class="hero-image-container">
33844
- @if (data.heroImage) {
33845
- <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
33846
- } @else {
33847
- <div class="hero-image-placeholder">
33848
- <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
33849
- </div>
33850
- }
33851
- </div>
33852
-
33853
- <div class="booking-summary-card">
33854
- <div class="details-section">
33855
- <h3 class="details-heading">Booking detaljer</h3>
33856
- <div class="info-rows">
33857
- @if (data.bookingDate) {
33858
- <div class="info-row">
33859
- <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
33860
- <span>{{ data.bookingDate }}</span>
33861
- </div>
33862
- }
33863
- @if (data.bookingTime) {
33864
- <div class="info-row">
33865
- <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
33866
- <span>{{ data.bookingTime }}</span>
33867
- </div>
33868
- }
33869
- @if (data.price) {
33870
- <div class="info-row">
33871
- <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
33872
- <span>{{ data.price }}</span>
33873
- </div>
33874
- }
33875
- @if (data.bookingType) {
33876
- <div class="info-row">
33877
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
33878
- <span>{{ data.bookingType }}</span>
33879
- </div>
33880
- }
33881
- @for (req of data.requirements || []; track req) {
33882
- <div class="info-row">
33883
- <ds-icon name="remixLockLine" size="16px" color="tertiary" />
33884
- <span>{{ req }}</span>
33885
- </div>
33886
- }
33887
- </div>
33888
- </div>
33889
- </div>
33890
-
33891
- @if (data.canCancel) {
33892
- <div class="footer-button-container">
33893
- <div class="booking-actions-column">
33894
- <ds-button
33895
- size="md"
33896
- variant="secondary"
33897
- (clicked)="cancelBooking()">
33898
- Annuller booking
33899
- </ds-button>
33900
- </div>
33901
- </div>
33902
- }
33903
- </div>
33904
- </ds-mobile-bottom-sheet-wrapper>
33905
- }
33906
- `, isInline: true, styles: [":host{display:block;height:auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 8px}.detail-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px}.detail-content{padding:8px 20px 24px;display:flex;flex-direction:column;gap:20px}.hero-image-container{width:100%;aspect-ratio:16 / 9;border-radius:12px;overflow:hidden;background:var(--color-surface-secondary, #f5f5f5)}.hero-image{width:100%;height:100%;object-fit:cover;display:block}.hero-image-placeholder{width:100%;height:100%;min-height:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,color-mix(in srgb,var(--color-accent, #6B5FF5) 30%,transparent),color-mix(in srgb,var(--color-accent, #6B5FF5) 60%,transparent))}.booking-summary-card{background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:16px;padding:16px}.details-section{display:flex;flex-direction:column;gap:12px}.details-heading{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0}.info-rows{display:flex;flex-direction:column;gap:8px}.info-row{display:flex;align-items:center;gap:10px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #545B66);line-height:1.4}.info-row ds-icon{flex-shrink:0}.footer-button-container{width:100%;padding:16px 20px 0}.footer-button-container ::ng-deep ds-button{display:block;width:100%}.footer-button-container ::ng-deep ds-button button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px}.booking-action{padding:16px 20px;padding-bottom:calc(16px + max(8px,env(safe-area-inset-bottom,0px) - 24px));background:var(--color-surface-primary, #ffffff)}.booking-actions-row{display:flex;flex-direction:row;align-items:center;gap:12px;width:100%}.booking-action .cancel-primary{flex:1;min-width:0;display:block}.booking-action .cancel-primary ::ng-deep button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px;padding-left:16px;padding-right:16px}:host.presentation-modal{display:block;height:auto;width:100%;min-height:0;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsMobileBottomSheetWrapperComponent, selector: "ds-mobile-bottom-sheet-wrapper", inputs: ["showDragHandle"] }, { kind: "component", type: DsMobileModalBaseComponent, selector: "ds-mobile-modal-base", inputs: ["headerTitleInteractive", "showHeader"], outputs: ["titleClick"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }] });
33907
- }
33908
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetComponent, decorators: [{
33909
- type: Component,
33910
- args: [{ selector: 'ds-mobile-booking-detail-sheet', standalone: true, imports: [
33911
- CommonModule,
33912
- DsMobileBottomSheetWrapperComponent,
33913
- DsMobileModalBaseComponent,
33914
- DsMobileSectionComponent,
33915
- DsButtonComponent,
33916
- DsIconComponent,
33917
- DsIconButtonComponent,
33918
- ], template: `
33919
- @if (presentation === 'modal') {
33920
- <ds-mobile-modal-base
33921
- [headerTitle]="data.facilityTitle"
33922
- [closeButtonLabel]="'Luk'"
33923
- [isAutoHeight]="autoHeight"
33924
- [keyboardContentBehavior]="'follow'">
33925
-
33926
- <ds-mobile-section
33927
- [showBorder]="false"
33928
- padding="20px 20px 0 20px">
33929
- <div class="hero-image-container">
33930
- @if (data.heroImage) {
33931
- <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
33932
- } @else {
33933
- <div class="hero-image-placeholder">
33934
- <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
33935
- </div>
33936
- }
33937
- </div>
33938
- </ds-mobile-section>
33939
-
33940
- <ds-mobile-section padding="20px 20px 20px 20px">
33941
- <div class="booking-summary-card">
33942
- <div class="details-section">
33943
- <h3 class="details-heading">Booking detaljer</h3>
33944
- <div class="info-rows">
33945
- @if (data.bookingDate) {
33946
- <div class="info-row">
33947
- <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
33948
- <span>{{ data.bookingDate }}</span>
33949
- </div>
33950
- }
33951
- @if (data.bookingTime) {
33952
- <div class="info-row">
33953
- <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
33954
- <span>{{ data.bookingTime }}</span>
33955
- </div>
33956
- }
33957
- @if (data.price) {
33958
- <div class="info-row">
33959
- <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
33960
- <span>{{ data.price }}</span>
33961
- </div>
33962
- }
33963
- @if (data.bookingType) {
33964
- <div class="info-row">
33965
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
33966
- <span>{{ data.bookingType }}</span>
33967
- </div>
33968
- }
33969
- @for (req of data.requirements || []; track req) {
33970
- <div class="info-row">
33971
- <ds-icon name="remixLockLine" size="16px" color="tertiary" />
33972
- <span>{{ req }}</span>
33973
- </div>
33974
- }
33975
- </div>
33976
- </div>
33977
- </div>
33978
- </ds-mobile-section>
33979
-
33980
- @if (data.canCancel) {
33981
- <div class="booking-action">
33982
- <div class="booking-actions-row">
33983
- <ds-button
33984
- class="cancel-primary"
33985
- size="md"
33986
- variant="secondary"
33987
- (clicked)="cancelBooking()">
33988
- Annuller booking
33989
- </ds-button>
33990
- </div>
33991
- </div>
33992
- }
33993
- </ds-mobile-modal-base>
33994
- } @else {
33995
- <ds-mobile-bottom-sheet-wrapper [showDragHandle]="true">
33996
- <div class="detail-header">
33997
- <h2 class="detail-title">{{ data.facilityTitle }}</h2>
33998
- <ds-icon-button
33999
- icon="remixCloseLine"
34000
- variant="ghost"
34001
- size="sm"
34002
- (clicked)="close()"
34003
- aria-label="Luk"
34004
- />
34005
- </div>
34006
-
34007
- <div class="detail-content">
34008
- <div class="hero-image-container">
34009
- @if (data.heroImage) {
34010
- <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
34011
- } @else {
34012
- <div class="hero-image-placeholder">
34013
- <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
34014
- </div>
34015
- }
34016
- </div>
34017
-
34018
- <div class="booking-summary-card">
34019
- <div class="details-section">
34020
- <h3 class="details-heading">Booking detaljer</h3>
34021
- <div class="info-rows">
34022
- @if (data.bookingDate) {
34023
- <div class="info-row">
34024
- <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
34025
- <span>{{ data.bookingDate }}</span>
34026
- </div>
34027
- }
34028
- @if (data.bookingTime) {
34029
- <div class="info-row">
34030
- <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
34031
- <span>{{ data.bookingTime }}</span>
34032
- </div>
34033
- }
34034
- @if (data.price) {
34035
- <div class="info-row">
34036
- <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
34037
- <span>{{ data.price }}</span>
34038
- </div>
34039
- }
34040
- @if (data.bookingType) {
34041
- <div class="info-row">
34042
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
34043
- <span>{{ data.bookingType }}</span>
34044
- </div>
34045
- }
34046
- @for (req of data.requirements || []; track req) {
34047
- <div class="info-row">
34048
- <ds-icon name="remixLockLine" size="16px" color="tertiary" />
34049
- <span>{{ req }}</span>
34050
- </div>
34051
- }
34052
- </div>
34053
- </div>
34054
- </div>
34055
-
34056
- @if (data.canCancel) {
34057
- <div class="footer-button-container">
34058
- <div class="booking-actions-column">
34059
- <ds-button
34060
- size="md"
34061
- variant="secondary"
34062
- (clicked)="cancelBooking()">
34063
- Annuller booking
34064
- </ds-button>
34065
- </div>
34066
- </div>
34067
- }
34068
- </div>
34069
- </ds-mobile-bottom-sheet-wrapper>
34070
- }
34071
- `, styles: [":host{display:block;height:auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 8px}.detail-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px}.detail-content{padding:8px 20px 24px;display:flex;flex-direction:column;gap:20px}.hero-image-container{width:100%;aspect-ratio:16 / 9;border-radius:12px;overflow:hidden;background:var(--color-surface-secondary, #f5f5f5)}.hero-image{width:100%;height:100%;object-fit:cover;display:block}.hero-image-placeholder{width:100%;height:100%;min-height:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,color-mix(in srgb,var(--color-accent, #6B5FF5) 30%,transparent),color-mix(in srgb,var(--color-accent, #6B5FF5) 60%,transparent))}.booking-summary-card{background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:16px;padding:16px}.details-section{display:flex;flex-direction:column;gap:12px}.details-heading{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0}.info-rows{display:flex;flex-direction:column;gap:8px}.info-row{display:flex;align-items:center;gap:10px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #545B66);line-height:1.4}.info-row ds-icon{flex-shrink:0}.footer-button-container{width:100%;padding:16px 20px 0}.footer-button-container ::ng-deep ds-button{display:block;width:100%}.footer-button-container ::ng-deep ds-button button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px}.booking-action{padding:16px 20px;padding-bottom:calc(16px + max(8px,env(safe-area-inset-bottom,0px) - 24px));background:var(--color-surface-primary, #ffffff)}.booking-actions-row{display:flex;flex-direction:row;align-items:center;gap:12px;width:100%}.booking-action .cancel-primary{flex:1;min-width:0;display:block}.booking-action .cancel-primary ::ng-deep button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px;padding-left:16px;padding-right:16px}:host.presentation-modal{display:block;height:auto;width:100%;min-height:0;overflow:hidden}\n"] }]
34072
- }], ctorParameters: () => [{ type: i1.ModalController }], propDecorators: { presentation: [{
34073
- type: Input
34074
- }], data: [{
34075
- type: Input
34076
- }], autoHeight: [{
34077
- type: Input
34078
- }], isModalPresentation: [{
34079
- type: HostBinding,
34080
- args: ['class.presentation-modal']
34081
- }] } });
34082
-
34083
- class DsMobileBookingDetailSheetService extends BaseModalService {
34084
- bottomSheet;
34085
- constructor(modalController, bottomSheet) {
34086
- super(modalController);
34087
- this.bottomSheet = bottomSheet;
34088
- }
34089
- /**
34090
- * Bottom-sheet presentation (draggable breakpoints). Prefer `openAsModal` for booking lists.
34091
- */
34092
- async open(data) {
34093
- const modal = await this.bottomSheet.create({
34094
- component: DsMobileBookingDetailSheetComponent,
34095
- componentProps: { data },
34096
- autoHeight: true,
34097
- backdropDismiss: true,
34098
- backdropBlur: true,
34099
- });
34100
- const result = await modal.onWillDismiss();
34101
- return { role: result.role, data: result.data ?? undefined };
34102
- }
34103
- /** `ds-modal-base` shell — used for active and past bookings. Auto-heights to content. */
34104
- async openAsModal(data) {
34105
- const modal = await this.createModal(DsMobileBookingDetailSheetComponent, { data, presentation: 'modal', autoHeight: true }, { keyboardClose: true, autoHeight: true });
34106
- await modal.present();
34107
- const result = await modal.onWillDismiss();
34108
- return { role: result.role, data: result.data ?? undefined };
34109
- }
34110
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, deps: [{ token: i1.ModalController }, { token: DsMobileBottomSheetService }], target: i0.ɵɵFactoryTarget.Injectable });
34111
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, providedIn: 'root' });
34112
- }
34113
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, decorators: [{
34114
- type: Injectable,
34115
- args: [{
34116
- providedIn: 'root',
34117
- }]
34118
- }], ctorParameters: () => [{ type: i1.ModalController }, { type: DsMobileBottomSheetService }] });
34119
-
34120
35365
  class DsMobileBookingCancelConfirmationComponent {
34121
35366
  facilityTitle;
34122
35367
  facilityThumbnail;
@@ -34217,6 +35462,8 @@ class MobileBookingPageComponent {
34217
35462
  userService;
34218
35463
  pageComponent;
34219
35464
  bookingsSwiper;
35465
+ notificationModal = inject(DsMobileNotificationModalService);
35466
+ notificationService = inject(NotificationService);
34220
35467
  cancellingBookingId = signal(null, ...(ngDevMode ? [{ debugName: "cancellingBookingId" }] : []));
34221
35468
  lang = signal(resolveLanguage(), ...(ngDevMode ? [{ debugName: "lang" }] : []));
34222
35469
  historyLinkText = computed(() => HISTORY_LINK[this.lang()] ?? HISTORY_LINK['da'], ...(ngDevMode ? [{ debugName: "historyLinkText" }] : []));
@@ -34422,6 +35669,11 @@ class MobileBookingPageComponent {
34422
35669
  }
34423
35670
  ], ...(ngDevMode ? [{ debugName: "availableFacilities" }] : []));
34424
35671
  visibleFacilities = computed(() => this.availableFacilities().filter((f) => !f.archived), ...(ngDevMode ? [{ debugName: "visibleFacilities" }] : []));
35672
+ async handleNotificationClick() {
35673
+ const tapped = await this.notificationModal.open();
35674
+ if (tapped)
35675
+ console.log('Notification tapped:', tapped.type, tapped.id);
35676
+ }
34425
35677
  handleRefresh(event) {
34426
35678
  console.log('Pull-to-refresh triggered');
34427
35679
  // Check if offline and complete immediately
@@ -34712,8 +35964,10 @@ class MobileBookingPageComponent {
34712
35964
  <ds-mobile-page-main
34713
35965
  #pageComponent
34714
35966
  [title]="'Bookinger'"
35967
+ [notificationCount]="notificationService.unreadCount()"
34715
35968
  [avatarInitials]="userService.avatarInitials()"
34716
35969
  [avatarType]="userService.avatarType()"
35970
+ (notificationClick)="handleNotificationClick()"
34717
35971
  (refresh)="handleRefresh($event)">
34718
35972
 
34719
35973
  @if (pageComponent.isOffline()) {
@@ -34818,7 +36072,7 @@ class MobileBookingPageComponent {
34818
36072
  ariaLabel="Opret facilitet"
34819
36073
  (clicked)="openFacilityCreationModal()">
34820
36074
  </ds-mobile-fab>
34821
- `, isInline: true, styles: [".bookings-swiper-wrapper{padding:0;position:relative}.swiper-nav-buttons{display:contents}.swiper-nav-button{position:absolute;top:50%;transform:translateY(-50%);z-index:10}.swiper-nav-button:first-child{left:-48px}.swiper-nav-button:last-child{right:-48px}::ng-deep .swiper-nav-button button{border-radius:50%!important;width:48px!important;height:48px!important;padding:0!important}@media (max-width: 767px){.swiper-nav-buttons{display:none}}::ng-deep .bookings-swiper .swiper-slide{width:100%;max-width:600px;height:auto;pointer-events:auto}@media (min-width: 768px){::ng-deep .bookings-swiper .swiper-slide{max-width:100%}}::ng-deep .bookings-swiper .swiper-slide ds-mobile-interactive-list-item-booking{width:100%;pointer-events:auto}.booking-slide-content{position:relative;width:100%}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemBookingComponent, selector: "ds-mobile-interactive-list-item-booking", inputs: ["thumbnail", "facilityTitle", "description", "bookingDate", "bookingTime", "availabilityStatus", "statusLabel", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["bookingClick", "longPress"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }, { kind: "component", type: DsMobileLoaderOverlayComponent, selector: "ds-mobile-loader-overlay", inputs: ["spinnerSize", "borderRadius"] }] });
36075
+ `, isInline: true, styles: [".bookings-swiper-wrapper{padding:0;position:relative}.swiper-nav-buttons{display:contents}.swiper-nav-button{position:absolute;top:50%;transform:translateY(-50%);z-index:10}.swiper-nav-button:first-child{left:-48px}.swiper-nav-button:last-child{right:-48px}::ng-deep .swiper-nav-button button{border-radius:50%!important;width:48px!important;height:48px!important;padding:0!important}@media (max-width: 767px){.swiper-nav-buttons{display:none}}::ng-deep .bookings-swiper .swiper-slide{width:100%;max-width:600px;height:auto;pointer-events:auto}@media (min-width: 768px){::ng-deep .bookings-swiper .swiper-slide{max-width:100%}}::ng-deep .bookings-swiper .swiper-slide ds-mobile-interactive-list-item-booking{width:100%;pointer-events:auto}.booking-slide-content{position:relative;width:100%}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showNotification", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemBookingComponent, selector: "ds-mobile-interactive-list-item-booking", inputs: ["thumbnail", "facilityTitle", "description", "bookingDate", "bookingTime", "availabilityStatus", "statusLabel", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["bookingClick", "longPress"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }, { kind: "component", type: DsMobileLoaderOverlayComponent, selector: "ds-mobile-loader-overlay", inputs: ["spinnerSize", "borderRadius"] }] });
34822
36076
  }
34823
36077
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileBookingPageComponent, decorators: [{
34824
36078
  type: Component,
@@ -34835,8 +36089,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
34835
36089
  <ds-mobile-page-main
34836
36090
  #pageComponent
34837
36091
  [title]="'Bookinger'"
36092
+ [notificationCount]="notificationService.unreadCount()"
34838
36093
  [avatarInitials]="userService.avatarInitials()"
34839
36094
  [avatarType]="userService.avatarType()"
36095
+ (notificationClick)="handleNotificationClick()"
34840
36096
  (refresh)="handleRefresh($event)">
34841
36097
 
34842
36098
  @if (pageComponent.isOffline()) {
@@ -36726,9 +37982,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
36726
37982
  class TenantChatPageComponent {
36727
37983
  modalCtrl = inject(ModalController);
36728
37984
  userService = inject(UserService);
37985
+ notificationModal = inject(DsMobileNotificationModalService);
37986
+ notificationService = inject(NotificationService);
36729
37987
  peerMessaging = inject(PeerMessagingService);
36730
37988
  peerChat = inject(PeerChatLauncherService);
36731
37989
  isPeerGroupConversation = isPeerGroupConversation;
37990
+ async handleNotificationClick() {
37991
+ const tapped = await this.notificationModal.open();
37992
+ if (tapped)
37993
+ console.log('Notification tapped:', tapped.type, tapped.id);
37994
+ }
36732
37995
  handleRefresh(event) {
36733
37996
  setTimeout(() => event.target.complete(), 1000);
36734
37997
  }
@@ -36755,8 +38018,10 @@ class TenantChatPageComponent {
36755
38018
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: TenantChatPageComponent, isStandalone: true, selector: "app-tenant-chat-page", host: { classAttribute: "ion-page" }, ngImport: i0, template: `
36756
38019
  <ds-mobile-page-main
36757
38020
  title="Beskeder"
38021
+ [notificationCount]="notificationService.unreadCount()"
36758
38022
  [avatarInitials]="userService.avatarInitials()"
36759
38023
  [avatarType]="userService.avatarType()"
38024
+ (notificationClick)="handleNotificationClick()"
36760
38025
  (refresh)="handleRefresh($event)">
36761
38026
 
36762
38027
  <ds-mobile-section contentGap="0px" padding="12px 20px 20px 20px" [showBorder]="false">
@@ -36815,7 +38080,7 @@ class TenantChatPageComponent {
36815
38080
  ariaLabel="Vælg beboer at skrive med"
36816
38081
  (clicked)="goToTenants()">
36817
38082
  </ds-mobile-fab>
36818
- `, isInline: true, styles: [".empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px;opacity:.4}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:0 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "timestamp", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "unreadStyle", "clickable", "align", "showAvatarBadge", "groupStackMembers", "groupCustomAvatarUrl", "groupStackExcludeParticipantId"], outputs: ["messageClick", "longPress"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }] });
38083
+ `, isInline: true, styles: [".empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px;opacity:.4}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:0 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showNotification", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "timestamp", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "unreadStyle", "clickable", "align", "showAvatarBadge", "groupStackMembers", "groupCustomAvatarUrl", "groupStackExcludeParticipantId"], outputs: ["messageClick", "longPress"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }] });
36819
38084
  }
36820
38085
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TenantChatPageComponent, decorators: [{
36821
38086
  type: Component,
@@ -36829,8 +38094,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
36829
38094
  }, template: `
36830
38095
  <ds-mobile-page-main
36831
38096
  title="Beskeder"
38097
+ [notificationCount]="notificationService.unreadCount()"
36832
38098
  [avatarInitials]="userService.avatarInitials()"
36833
38099
  [avatarType]="userService.avatarType()"
38100
+ (notificationClick)="handleNotificationClick()"
36834
38101
  (refresh)="handleRefresh($event)">
36835
38102
 
36836
38103
  <ds-mobile-section contentGap="0px" padding="12px 20px 20px 20px" [showBorder]="false">
@@ -36972,6 +38239,8 @@ const MOCK_VENDORS = [
36972
38239
  class ServicesPageComponent {
36973
38240
  pageComponent;
36974
38241
  vendorModal = inject(DsMobileServiceVendorModalService);
38242
+ notificationModal = inject(DsMobileNotificationModalService);
38243
+ notificationService = inject(NotificationService);
36975
38244
  newInquiryModal = inject(DsMobileNewInquiryModalService);
36976
38245
  inquiriesService = inject(InquiriesService);
36977
38246
  navCtrl = inject(NavController);
@@ -37018,6 +38287,11 @@ class ServicesPageComponent {
37018
38287
  },
37019
38288
  });
37020
38289
  }
38290
+ async handleNotificationClick() {
38291
+ const tapped = await this.notificationModal.open();
38292
+ if (tapped)
38293
+ console.log('Notification tapped:', tapped.type, tapped.id);
38294
+ }
37021
38295
  handleRefresh(event) {
37022
38296
  setTimeout(() => {
37023
38297
  event.target?.complete?.();
@@ -37028,8 +38302,10 @@ class ServicesPageComponent {
37028
38302
  <ds-mobile-page-main
37029
38303
  #pageComponent
37030
38304
  [title]="lbl.pageTitle"
38305
+ [notificationCount]="notificationService.unreadCount()"
37031
38306
  [avatarInitials]="userService.avatarInitials()"
37032
38307
  [avatarType]="userService.avatarType()"
38308
+ (notificationClick)="handleNotificationClick()"
37033
38309
  (refresh)="handleRefresh($event)"
37034
38310
  >
37035
38311
  @if (pageComponent.isOffline()) {
@@ -37061,7 +38337,7 @@ class ServicesPageComponent {
37061
38337
  </ds-mobile-section>
37062
38338
  }
37063
38339
  </ds-mobile-page-main>
37064
- `, isInline: true, styles: [".empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobileInteractiveListItemServiceComponent, selector: "ds-mobile-interactive-list-item-service", inputs: ["title", "description", "logo", "showChevron"], outputs: ["serviceClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }] });
38340
+ `, isInline: true, styles: [".empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showNotification", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobileInteractiveListItemServiceComponent, selector: "ds-mobile-interactive-list-item-service", inputs: ["title", "description", "logo", "showChevron"], outputs: ["serviceClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }] });
37065
38341
  }
37066
38342
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ServicesPageComponent, decorators: [{
37067
38343
  type: Component,
@@ -37075,8 +38351,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
37075
38351
  <ds-mobile-page-main
37076
38352
  #pageComponent
37077
38353
  [title]="lbl.pageTitle"
38354
+ [notificationCount]="notificationService.unreadCount()"
37078
38355
  [avatarInitials]="userService.avatarInitials()"
37079
38356
  [avatarType]="userService.avatarType()"
38357
+ (notificationClick)="handleNotificationClick()"
37080
38358
  (refresh)="handleRefresh($event)"
37081
38359
  >
37082
38360
  @if (pageComponent.isOffline()) {
@@ -37114,6 +38392,122 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
37114
38392
  args: ['pageComponent']
37115
38393
  }] } });
37116
38394
 
38395
+ const PREFS = [
38396
+ { key: 'messages', label: 'Beskeder', description: 'Nye beskeder fra beboere og administration' },
38397
+ { key: 'bookings', label: 'Bookinger', description: 'Bekræftelser, aflysninger og påmindelser' },
38398
+ { key: 'community', label: 'Fællesskab', description: 'Opslag, kommentarer og omtaler' },
38399
+ { key: 'inquiries', label: 'Henvendelser', description: 'Opdateringer på dine henvendelser' },
38400
+ { key: 'services', label: 'Service', description: 'Opdateringer fra serviceleverandører' },
38401
+ { key: 'handbook', label: 'Håndbog', description: 'Ændringer i beboerhåndbogen' },
38402
+ { key: 'system', label: 'System', description: 'Meddelelser, invitationer og familieadgang' },
38403
+ ];
38404
+ class SettingsModalComponent {
38405
+ notificationService = inject(NotificationService);
38406
+ prefs = PREFS;
38407
+ enabledSignals = new Map(PREFS.map(p => [p.key, computed(() => this.notificationService.isPushEnabled(p.key))]));
38408
+ isEnabled(key) {
38409
+ return this.enabledSignals.get(key);
38410
+ }
38411
+ toggle(key) {
38412
+ const current = this.notificationService.isPushEnabled(key);
38413
+ this.notificationService.setPushEnabled(key, !current);
38414
+ Haptics.impact({ style: ImpactStyle.Light }).catch(() => {
38415
+ if ('vibrate' in navigator)
38416
+ navigator.vibrate(10);
38417
+ });
38418
+ }
38419
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SettingsModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
38420
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: SettingsModalComponent, isStandalone: true, selector: "app-settings-modal", ngImport: i0, template: `
38421
+ <ds-mobile-modal-base headerTitle="Notifikationer" [showHeader]="true">
38422
+
38423
+ <div class="settings-content">
38424
+ @for (pref of prefs; track pref.key; let isFirst = $first; let isLast = $last) {
38425
+ <ds-mobile-list-item
38426
+ [showDivider]="!isLast"
38427
+ [flushTop]="isFirst"
38428
+ [interactive]="true"
38429
+ [enableLongPress]="false"
38430
+ [moreActions]="false"
38431
+ [title]="pref.label"
38432
+ [subtitle]="pref.description"
38433
+ (itemClick)="toggle(pref.key)"
38434
+ >
38435
+ <ds-mobile-toggle
38436
+ content-trailing
38437
+ [checked]="isEnabled(pref.key)()"
38438
+ (changed)="toggle(pref.key)"
38439
+ />
38440
+ </ds-mobile-list-item>
38441
+ }
38442
+ </div>
38443
+
38444
+ </ds-mobile-modal-base>
38445
+ `, isInline: true, styles: [".settings-content{padding:20px}\n"], dependencies: [{ kind: "component", type: DsMobileModalBaseComponent, selector: "ds-mobile-modal-base", inputs: ["headerTitleInteractive", "showHeader"], outputs: ["titleClick"] }, { kind: "component", type: DsMobileListItemComponent, selector: "ds-mobile-list-item", inputs: ["leadingSize", "variant", "align", "flushTop", "interactive", "disabled", "loading", "enableLongPress", "showDesktopMoreButton", "moreActions", "moreButtonAriaLabel", "interactiveOffset", "title", "subtitle", "showDivider", "dividerSpacing"], outputs: ["itemClick", "moreButtonClick"] }, { kind: "component", type: DsMobileToggleComponent, selector: "ds-mobile-toggle", inputs: ["checked", "disabled"], outputs: ["checkedChange", "changed"] }] });
38446
+ }
38447
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SettingsModalComponent, decorators: [{
38448
+ type: Component,
38449
+ args: [{ selector: 'app-settings-modal', standalone: true, imports: [
38450
+ DsMobileModalBaseComponent,
38451
+ DsMobileListItemComponent,
38452
+ DsMobileToggleComponent,
38453
+ ], template: `
38454
+ <ds-mobile-modal-base headerTitle="Notifikationer" [showHeader]="true">
38455
+
38456
+ <div class="settings-content">
38457
+ @for (pref of prefs; track pref.key; let isFirst = $first; let isLast = $last) {
38458
+ <ds-mobile-list-item
38459
+ [showDivider]="!isLast"
38460
+ [flushTop]="isFirst"
38461
+ [interactive]="true"
38462
+ [enableLongPress]="false"
38463
+ [moreActions]="false"
38464
+ [title]="pref.label"
38465
+ [subtitle]="pref.description"
38466
+ (itemClick)="toggle(pref.key)"
38467
+ >
38468
+ <ds-mobile-toggle
38469
+ content-trailing
38470
+ [checked]="isEnabled(pref.key)()"
38471
+ (changed)="toggle(pref.key)"
38472
+ />
38473
+ </ds-mobile-list-item>
38474
+ }
38475
+ </div>
38476
+
38477
+ </ds-mobile-modal-base>
38478
+ `, styles: [".settings-content{padding:20px}\n"] }]
38479
+ }] });
38480
+
38481
+ class SettingsModalService {
38482
+ modalController;
38483
+ constructor(modalController) {
38484
+ this.modalController = modalController;
38485
+ }
38486
+ async open() {
38487
+ const modal = await this.modalController.create({
38488
+ component: SettingsModalComponent,
38489
+ cssClass: 'ds-modal-base',
38490
+ mode: 'ios',
38491
+ presentingElement: document.querySelector('ion-router-outlet') || undefined,
38492
+ backdropDismiss: true,
38493
+ showBackdrop: true,
38494
+ animated: true,
38495
+ });
38496
+ await modal.present();
38497
+ }
38498
+ async close(data) {
38499
+ return this.modalController.dismiss(data);
38500
+ }
38501
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SettingsModalService, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Injectable });
38502
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SettingsModalService, providedIn: 'root' });
38503
+ }
38504
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SettingsModalService, decorators: [{
38505
+ type: Injectable,
38506
+ args: [{
38507
+ providedIn: 'root',
38508
+ }]
38509
+ }], ctorParameters: () => [{ type: i1.ModalController }] });
38510
+
37117
38511
  /**
37118
38512
  * Services Barrel File
37119
38513
  *
@@ -37137,5 +38531,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
37137
38531
  * Generated bundle index. Do not edit.
37138
38532
  */
37139
38533
 
37140
- export { AcceptInvitePageComponent, ActionCommentComponent, ActionLikeComponent, AvatarUploadPageComponent, BaseModalService, ContentRowComponent, CreateAccountPageComponent, DEFAULT_SERVICE_PAGE_LABELS, DsAppIconComponent, DsAvatarWithBadgeComponent, DsLogoComponent, DsMobileAccessSheetComponent, DsMobileActionListItemComponent, DsMobileActionsBottomSheetComponent, DsMobileAddGroupTenantsModalComponent, DsMobileAppLoadingComponent, DsMobileAttachmentPreviewComponent, DsMobileBookingConfirmationWrapperComponent, DsMobileBookingModalComponent, DsMobileBookingModalService, DsMobileBookingSummaryComponent, DsMobileBottomSheetHeaderComponent, DsMobileBottomSheetService, DsMobileBottomSheetWrapperComponent, DsMobileCapacitySheetComponent, DsMobileCardInlineBannerComponent, DsMobileCardInlineComponent, DsMobileCardInlineContactComponent, DsMobileCardInlineFileComponent, DsMobileChatModalComponent, DsMobileChatModalService, DsMobileActionsBottomSheetComponent as DsMobileCommentActionsBottomSheetComponent, DsMobileCommentComponent, DsMobileCommunityAdminPickerComponent, DsMobileCommunityAdminsModalComponent, DsMobileConfirmationSheetComponent, DsMobileContactListItemComponent, DsMobileContentComponent, DsMobileCreateGroupModalComponent, DsMobileDropdownComponent, DsMobileEditGroupModalComponent, DsMobileEmptyStateComponent, DsMobileFabComponent, DsMobileFacilityArchiveConfirmationComponent, DsMobileFacilityCreationConfirmationWrapperComponent, DsMobileFacilityCreationModalComponent, DsMobileFacilityCreationModalService, DsMobileFacilityDeleteConfirmationComponent, DsMobileFacilityDetailModalComponent, DsMobileFacilityDetailModalService, DsMobileFileAttachmentComponent, DsMobileGlassSpinnerComponent, DsMobileGroupAvatarStackComponent, DsMobileGroupMembersModalComponent, DsMobileHandbookDetailModalComponent, DsMobileHandbookDetailModalService, DsMobileHandbookFolderComponent, DsMobileHandbookFolderMiniComponent, DsMobileHeaderContentComponent, DsMobileHeaderContentTileComponent, DsMobileIllustrationComponent, DsMobileImagePlaceholderComponent, DsMobileInlinePhotoComponent, DsMobileInlineTabsComponent, DsMobileInteractiveListItemBookingComponent, DsMobileInteractiveListItemInquiryComponent, DsMobileInteractiveListItemMessageComponent, DsMobileInteractiveListItemPostComponent, DsMobileLightboxImageComponent as DsMobileLightboxComponent, DsMobileLightboxFooterComponent, DsMobileLightboxHeaderComponent, DsMobileLightboxImageComponent, DsMobileLightboxImageWithDescriptionComponent, DsMobileLightboxPdfComponent, DsMobileLightboxService, DsMobileListItemComponent, DsMobileListItemStaticComponent, DsMobileListSearchComponent, DsMobileLoaderOverlayComponent, DsMobileLongPressDirective, DsMobileMediaActionsPanelComponent, DsMobileMessageBubbleComponent, DsMobileMessageComposerComponent, DsMobileModalBaseComponent, DsMobileModalService, DsMobileNewInquiryModalComponent, DsMobileNewInquiryModalService, DsMobileOfflineBannerComponent, DsMobilePageDetailsComponent, DsMobilePageMainComponent, DsMobilePillComponent, DsMobileActionsBottomSheetComponent as DsMobilePostActionsBottomSheetComponent, DsMobilePostComposerComponent, DsMobilePostCreateBottomSheetComponent, DsMobilePostDetailModalComponent, DsMobilePostDetailModalService, DsMobilePriceSheetComponent, DsMobileProfileActionsSheetComponent, DsMobilePromptBottomSheetComponent, DsMobilePropertyBannerComponent, DsMobileRichTextEditorComponent, DsMobileSectionComponent, DsMobileServiceVendorModalService, DsMobileServiceVendorSheetComponent, DsMobileSwiperComponent, DsMobileSwiperWithNavComponent, DsMobileSystemMessageBannerComponent, DsMobileTabBarComponent, DsMobileTabsComponent, DsMobileTenantPickerModalComponent, DsMobileWhenCanBookSheetComponent, DsMobileWhoCanBookSheetComponent, DsTextInputComponent, FamilyAccessPageComponent, FamilyAccessService, InquiriesService, InviteSuccessPageComponent, MediaPickerService, MobileBookingPageComponent, MobileCommunityPageComponent, MobileHandbookPageComponent, MobileHomePageComponent, MobileInquiriesPageComponent, MobileInquiryDetailPageComponent, MobileModalBase, MobilePageBase, MobilePostDetailPageComponent, MobileTabsExampleComponent, PageLoadingService, PostActionsComponent, PostAttachmentsComponent, PostContentComponent, PostCreatePageComponent, PostMediaComponent, PostPdfAttachmentComponent, PostTextComponent, PostsService, SectionHeaderComponent, ServicesPageComponent, SignInPageComponent, SignInToAcceptPageComponent, TenantChatPageComponent, TileContentComponent, TileIconComponent, TileLabelComponent, TileValueComponent, TrackingPermissionService, UserService, WhitelabelDemoModalComponent, WhitelabelDemoModalService, WhitelabelService, customBackTransition, customPageTransition };
38534
+ export { AcceptInvitePageComponent, ActionCommentComponent, ActionLikeComponent, AvatarUploadPageComponent, BaseModalService, ContentRowComponent, CreateAccountPageComponent, DEFAULT_SERVICE_PAGE_LABELS, DsAppIconComponent, DsAvatarWithBadgeComponent, DsLogoComponent, DsMobileAccessSheetComponent, DsMobileActionListItemComponent, DsMobileActionsBottomSheetComponent, DsMobileAddGroupTenantsModalComponent, DsMobileAppLoadingComponent, DsMobileAttachmentPreviewComponent, DsMobileBookingConfirmationWrapperComponent, DsMobileBookingModalComponent, DsMobileBookingModalService, DsMobileBookingSummaryComponent, DsMobileBottomSheetHeaderComponent, DsMobileBottomSheetService, DsMobileBottomSheetWrapperComponent, DsMobileCapacitySheetComponent, DsMobileCardInlineBannerComponent, DsMobileCardInlineComponent, DsMobileCardInlineContactComponent, DsMobileCardInlineFileComponent, DsMobileChatModalComponent, DsMobileChatModalService, DsMobileActionsBottomSheetComponent as DsMobileCommentActionsBottomSheetComponent, DsMobileCommentComponent, DsMobileCommunityAdminPickerComponent, DsMobileCommunityAdminsModalComponent, DsMobileConfirmationSheetComponent, DsMobileContactListItemComponent, DsMobileContentComponent, DsMobileCountBadgeComponent, DsMobileCreateGroupModalComponent, DsMobileDropdownComponent, DsMobileEditGroupModalComponent, DsMobileEmptyStateComponent, DsMobileFabComponent, DsMobileFacilityArchiveConfirmationComponent, DsMobileFacilityCreationConfirmationWrapperComponent, DsMobileFacilityCreationModalComponent, DsMobileFacilityCreationModalService, DsMobileFacilityDeleteConfirmationComponent, DsMobileFacilityDetailModalComponent, DsMobileFacilityDetailModalService, DsMobileFileAttachmentComponent, DsMobileGlassSpinnerComponent, DsMobileGroupAvatarStackComponent, DsMobileGroupMembersModalComponent, DsMobileHandbookDetailModalComponent, DsMobileHandbookDetailModalService, DsMobileHandbookFolderComponent, DsMobileHandbookFolderMiniComponent, DsMobileHeaderContentComponent, DsMobileHeaderContentTileComponent, DsMobileIllustrationComponent, DsMobileImagePlaceholderComponent, DsMobileInlinePhotoComponent, DsMobileInlineTabsComponent, DsMobileInteractiveListItemBookingComponent, DsMobileInteractiveListItemInquiryComponent, DsMobileInteractiveListItemMessageComponent, DsMobileInteractiveListItemPostComponent, DsMobileLightboxImageComponent as DsMobileLightboxComponent, DsMobileLightboxFooterComponent, DsMobileLightboxHeaderComponent, DsMobileLightboxImageComponent, DsMobileLightboxImageWithDescriptionComponent, DsMobileLightboxPdfComponent, DsMobileLightboxService, DsMobileListItemComponent, DsMobileListItemStaticComponent, DsMobileListSearchComponent, DsMobileLoaderOverlayComponent, DsMobileLongPressDirective, DsMobileMediaActionsPanelComponent, DsMobileMessageBubbleComponent, DsMobileMessageComposerComponent, DsMobileModalBaseComponent, DsMobileModalService, DsMobileNewInquiryModalComponent, DsMobileNewInquiryModalService, DsMobileNotificationButtonComponent, DsMobileNotificationModalComponent, DsMobileNotificationModalService, DsMobileNotificationPromptComponent, DsMobileOfflineBannerComponent, DsMobilePageDetailsComponent, DsMobilePageMainComponent, DsMobilePillComponent, DsMobileActionsBottomSheetComponent as DsMobilePostActionsBottomSheetComponent, DsMobilePostComposerComponent, DsMobilePostCreateBottomSheetComponent, DsMobilePostDetailModalComponent, DsMobilePostDetailModalService, DsMobilePriceSheetComponent, DsMobileProfileActionsSheetComponent, DsMobilePromptBottomSheetComponent, DsMobilePropertyBannerComponent, DsMobileRichTextEditorComponent, DsMobileSectionComponent, DsMobileServiceVendorModalService, DsMobileServiceVendorSheetComponent, DsMobileSwiperComponent, DsMobileSwiperWithNavComponent, DsMobileSystemMessageBannerComponent, DsMobileTabBarComponent, DsMobileTabsComponent, DsMobileTenantPickerModalComponent, DsMobileToggleComponent, DsMobileWhenCanBookSheetComponent, DsMobileWhoCanBookSheetComponent, DsTextInputComponent, FamilyAccessPageComponent, FamilyAccessService, InquiriesService, InviteSuccessPageComponent, MediaPickerService, MobileBookingPageComponent, MobileCommunityPageComponent, MobileHandbookPageComponent, MobileHomePageComponent, MobileInquiriesPageComponent, MobileInquiryDetailPageComponent, MobileModalBase, MobilePageBase, MobilePostDetailPageComponent, MobileTabsExampleComponent, NOTIFICATION_ICON_MAP, NotificationPromptService, NotificationService, PageLoadingService, PostActionsComponent, PostAttachmentsComponent, PostContentComponent, PostCreatePageComponent, PostMediaComponent, PostPdfAttachmentComponent, PostTextComponent, PostsService, RelativeTimePipe, SAMPLE_NOTIFICATIONS, SectionHeaderComponent, ServicesPageComponent, SettingsModalService, SignInPageComponent, SignInToAcceptPageComponent, TenantChatPageComponent, TileContentComponent, TileIconComponent, TileLabelComponent, TileValueComponent, TrackingPermissionService, UserService, VENDOR_MODAL_SERVICE, WhitelabelDemoModalComponent, WhitelabelDemoModalService, WhitelabelService, customBackTransition, customPageTransition, dateBucket };
37141
38535
  //# sourceMappingURL=propbinder-mobile-design.mjs.map