@propbinder/mobile-design 0.2.48 → 0.2.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/ng-package.json +24 -0
  2. package/package.json +3 -39
  3. package/src/animations/page-transitions.ts +165 -0
  4. package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
  5. package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
  6. package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
  7. package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
  8. package/src/components/action-list-item/index.ts +2 -0
  9. package/src/components/app-icon/ds-app-icon.ts +133 -0
  10. package/src/components/app-icon/index.ts +2 -0
  11. package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
  12. package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
  13. package/src/components/attachment-preview/index.ts +1 -0
  14. package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
  15. package/src/components/avatar-with-badge/index.ts +2 -0
  16. package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
  17. package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
  18. package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
  19. package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
  20. package/src/components/booking-modal/index.ts +4 -0
  21. package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
  22. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
  23. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
  24. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
  25. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
  26. package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
  27. package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
  28. package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
  29. package/src/components/bottom-sheet/index.ts +8 -0
  30. package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
  31. package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
  32. package/src/components/card-inline/index.ts +2 -0
  33. package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
  34. package/src/components/card-inline-banner/index.ts +1 -0
  35. package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
  36. package/src/components/card-inline-contact/index.ts +1 -0
  37. package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
  38. package/src/components/card-inline-file/index.ts +1 -0
  39. package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
  40. package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
  41. package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
  42. package/src/components/chat-modal/index.ts +8 -0
  43. package/src/components/comment/ds-mobile-comment.ts +568 -0
  44. package/src/components/comment/index.ts +2 -0
  45. package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
  46. package/src/components/contact-list-item/index.ts +2 -0
  47. package/src/components/content/ds-mobile-content.ts +139 -0
  48. package/src/components/content/index.ts +2 -0
  49. package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
  50. package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
  51. package/src/components/dropdown/index.ts +2 -0
  52. package/src/components/ds-mobile-tabs.css +407 -0
  53. package/src/components/ds-mobile-tabs.ts +216 -0
  54. package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
  55. package/src/components/empty-state/index.ts +2 -0
  56. package/src/components/fab/ds-mobile-fab.ts +315 -0
  57. package/src/components/fab/index.ts +1 -0
  58. package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
  59. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
  60. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
  61. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
  62. package/src/components/facility-creation-modal/index.ts +9 -0
  63. package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
  64. package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
  65. package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
  66. package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
  67. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
  68. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
  69. package/src/components/facility-detail-modal/index.ts +2 -0
  70. package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
  71. package/src/components/file-attachment/index.ts +2 -0
  72. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
  73. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
  74. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
  75. package/src/components/handbook-detail-modal/index.ts +3 -0
  76. package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
  77. package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
  78. package/src/components/handbook-folder/index.ts +4 -0
  79. package/src/components/header-content/ds-mobile-header-content.ts +222 -0
  80. package/src/components/header-content/index.ts +2 -0
  81. package/src/components/illustration/ds-mobile-illustration.ts +124 -0
  82. package/src/components/illustration/index.ts +2 -0
  83. package/src/components/index.ts +124 -0
  84. package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
  85. package/src/components/inline-photo/index.ts +1 -0
  86. package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
  87. package/src/components/inline-tabs/index.ts +2 -0
  88. package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
  89. package/src/components/interactive-list-item-booking/index.ts +1 -0
  90. package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
  91. package/src/components/interactive-list-item-inquiry/index.ts +2 -0
  92. package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
  93. package/src/components/interactive-list-item-message/index.ts +2 -0
  94. package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
  95. package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
  96. package/src/components/interactive-list-item-post/index.ts +13 -0
  97. package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
  98. package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
  99. package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
  100. package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
  101. package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
  102. package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
  103. package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
  104. package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
  105. package/src/components/lightbox/index.ts +22 -0
  106. package/src/components/list-item/ds-mobile-list-item.ts +603 -0
  107. package/src/components/list-item/index.ts +2 -0
  108. package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
  109. package/src/components/list-item-static/index.ts +2 -0
  110. package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
  111. package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
  112. package/src/components/loader-overlay/index.ts +1 -0
  113. package/src/components/logo/ds-logo.ts +95 -0
  114. package/src/components/logo/index.ts +2 -0
  115. package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
  116. package/src/components/message-bubble/index.ts +7 -0
  117. package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
  118. package/src/components/message-composer/index.ts +7 -0
  119. package/src/components/modal/ds-mobile-modal.css +163 -0
  120. package/src/components/modal/ds-mobile-modal.service.ts +329 -0
  121. package/src/components/modal/index.ts +8 -0
  122. package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
  123. package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
  124. package/src/components/modal-base/index.ts +2 -0
  125. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
  126. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
  127. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
  128. package/src/components/new-inquiry-modal/index.ts +4 -0
  129. package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
  130. package/src/components/offline-banner/index.ts +1 -0
  131. package/src/components/page-details/ds-mobile-page-details.css +83 -0
  132. package/src/components/page-details/ds-mobile-page-details.ts +282 -0
  133. package/src/components/page-details/index.ts +2 -0
  134. package/src/components/page-main/ds-mobile-page-main.css +68 -0
  135. package/src/components/page-main/ds-mobile-page-main.ts +421 -0
  136. package/src/components/page-main/index.ts +2 -0
  137. package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
  138. package/src/components/post-composer/index.ts +2 -0
  139. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
  140. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
  141. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
  142. package/src/components/post-detail-modal/index.ts +9 -0
  143. package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
  144. package/src/components/property-banner/index.ts +2 -0
  145. package/src/components/section/ds-mobile-section.ts +263 -0
  146. package/src/components/section/index.ts +2 -0
  147. package/src/components/shared/directives/index.ts +2 -0
  148. package/src/components/shared/directives/long-press.directive.ts +212 -0
  149. package/src/components/shared/index.ts +3 -0
  150. package/src/components/shared/mobile-modal-base.ts +457 -0
  151. package/src/components/shared/mobile-page-base.ts +204 -0
  152. package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
  153. package/src/components/swiper/ds-mobile-swiper.ts +327 -0
  154. package/src/components/swiper/index.ts +3 -0
  155. package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
  156. package/src/components/system-message-banner/index.ts +2 -0
  157. package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
  158. package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
  159. package/src/components/tab-bar/index.ts +2 -0
  160. package/src/components/tabs/ds-mobile-tabs.css +25 -0
  161. package/src/components/tabs/ds-mobile-tabs.ts +89 -0
  162. package/src/components/tabs/index.ts +2 -0
  163. package/src/components/text-input/ds-text-input.ts +287 -0
  164. package/src/components/text-input/index.ts +2 -0
  165. package/src/examples/booking.page.ts +434 -0
  166. package/src/examples/community.page.ts +776 -0
  167. package/src/examples/handbook.page.ts +324 -0
  168. package/src/examples/home.page.ts +347 -0
  169. package/src/examples/index.ts +12 -0
  170. package/src/examples/inquiries.example.ts +273 -0
  171. package/src/examples/inquiry-detail.example.css +189 -0
  172. package/src/examples/inquiry-detail.example.ts +415 -0
  173. package/src/examples/mobile-tabs-example.component.ts +208 -0
  174. package/src/examples/post-create.page.ts +311 -0
  175. package/src/examples/post-detail.page.ts +296 -0
  176. package/src/examples/sign-in.page.ts +291 -0
  177. package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
  178. package/src/examples/whitelabel-demo-modal.service.ts +77 -0
  179. package/src/models/index.ts +7 -0
  180. package/src/models/post.model.ts +41 -0
  181. package/src/pages/community.page.ts +769 -0
  182. package/src/pages/handbook.page.ts +388 -0
  183. package/src/pages/home.page.ts +303 -0
  184. package/src/pages/index.ts +11 -0
  185. package/src/pages/inquiries.example.ts +273 -0
  186. package/src/pages/inquiry-detail.example.css +189 -0
  187. package/src/pages/inquiry-detail.example.ts +415 -0
  188. package/src/pages/mobile-tabs-example.component.ts +179 -0
  189. package/src/pages/post-create.page.ts +311 -0
  190. package/src/pages/post-detail.page.ts +296 -0
  191. package/src/pages/sign-in.page.ts +291 -0
  192. package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
  193. package/src/pages/whitelabel-demo-modal.service.ts +77 -0
  194. package/src/public-api.ts +6 -0
  195. package/src/services/base-modal.service.ts +101 -0
  196. package/src/services/index.ts +11 -0
  197. package/src/services/posts.service.ts +542 -0
  198. package/src/services/tracking-permission.service.ts +88 -0
  199. package/src/services/user.service.ts +60 -0
  200. package/src/services/whitelabel.service.ts +675 -0
  201. package/{styles → src/styles}/ionic.css +25 -0
  202. package/tsconfig.lib.json +17 -0
  203. package/tsconfig.lib.prod.json +9 -0
  204. package/tsconfig.spec.json +13 -0
  205. package/fesm2022/propbinder-mobile-design.mjs +0 -26168
  206. package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
  207. package/index.d.ts +0 -8169
  208. /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
  209. /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
  210. /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
  211. /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
  212. /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
  213. /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
  214. /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
  215. /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
  216. /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
  217. /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
  218. /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
  219. /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
  220. /package/{styles → src/components/shared}/mobile-common.css +0 -0
  221. /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
@@ -0,0 +1,675 @@
1
+ import { Injectable, signal, effect, computed } from '@angular/core';
2
+ import { StatusBar, Style } from '@capacitor/status-bar';
3
+
4
+ export interface WhitelabelConfig {
5
+ // Logo assets
6
+ logoUrl: string; // Full logo for header (typically horizontal)
7
+ logoMarkUrl: string; // Compact logo mark for avatars/badges
8
+ logoAlt: string; // Alt text for accessibility
9
+ logoSize: 'sm' | 'md' | 'lg' | 'xl'; // Logo size in header (sm: 24px, md: 28px, lg: 32px, xl: 36px)
10
+
11
+ // Logo dimensions (optional, for optimization)
12
+ logoWidth?: number;
13
+ logoHeight?: number;
14
+ logoMarkWidth?: number;
15
+ logoMarkHeight?: number;
16
+
17
+ // ============================================
18
+ // APP ICON (app icons, logo badges)
19
+ // ============================================
20
+ appIconSurface: string; // App icon background fill
21
+ appIconContent: string; // Logomark color on app icon
22
+
23
+ // ============================================
24
+ // ACTIONS & SELECTIONS (buttons, FABs, active tabs, selected items)
25
+ // ============================================
26
+ accent: string; // Main accent color (button bg, active tab icon)
27
+ onAccent: string; // Content on accent-colored surfaces (button text/icon)
28
+
29
+ // ============================================
30
+ // HEADER/NAVIGATION (includes ion-header and header-expandable)
31
+ // ============================================
32
+ headerSurface: string; // Header background
33
+ headerContent: string; // Header text/icons
34
+ headerAccent: string; // Accent elements in header (e.g., subtle overlay)
35
+ onHeaderAccent: string; // Content on header accent elements
36
+
37
+ // ============================================
38
+ // SIGN-IN PAGE CUSTOMIZATION
39
+ // ============================================
40
+ showCityIllustration: boolean; // Show/hide city illustration on sign-in page
41
+
42
+ // Sign-in background
43
+ signInBgType: 'solid' | 'gradient'; // Background type
44
+ signInBgSolid: string; // Solid background color
45
+ signInBgGradientStart: string; // Gradient start color (top)
46
+ signInBgGradientEnd: string; // Gradient end color (bottom)
47
+
48
+ // Sign-in content color
49
+ signInContentColor: string; // Text color on sign-in page (headings, body text, links)
50
+
51
+ // Organization info
52
+ organizationName: string;
53
+ organizationId: string;
54
+ }
55
+
56
+ const DEFAULT_CONFIG: WhitelabelConfig = {
57
+ logoUrl: '/Assets/logos/propbinder-logomark.svg',
58
+ logoMarkUrl: '/Assets/logos/propbinder-logomark.svg',
59
+ logoAlt: 'Propbinder',
60
+ logoSize: 'md', // Propbinder default: md (28px mobile, 32px desktop)
61
+
62
+ // App icon (brand identity)
63
+ appIconSurface: '#6B5FF5', // Purple
64
+ appIconContent: '#FFFFFF', // White
65
+
66
+ // Accent (buttons, FABs, active tabs)
67
+ accent: '#6B5FF5', // Purple
68
+ onAccent: '#FFFFFF', // White
69
+
70
+ // Header/navigation
71
+ headerSurface: '#221a4c', // Dark purple
72
+ headerContent: '#FFFFFF', // White
73
+ headerAccent: '#6B5FF5', // Purple accent (matches Propbinder theme)
74
+ onHeaderAccent: '#FFFFFF', // White
75
+
76
+ // Sign-in page
77
+ showCityIllustration: true, // Show city illustration by default
78
+ signInBgType: 'gradient', // Use gradient by default
79
+ signInBgSolid: '#D6C7FF', // Fallback solid color
80
+ signInBgGradientStart: '#D6C7FF', // Gradient top color
81
+ signInBgGradientEnd: '#8A9BFF', // Gradient bottom color
82
+ signInContentColor: '#1a1a1a', // Default dark text color
83
+
84
+ organizationName: 'Propbinder',
85
+ organizationId: 'default',
86
+ };
87
+
88
+ /**
89
+ * WhitelabelService
90
+ *
91
+ * Manages whitelabel configuration including logos and brand colors.
92
+ * Automatically updates CSS custom properties when colors change.
93
+ *
94
+ * @example
95
+ * Initialize with custom config:
96
+ * ```typescript
97
+ * whitelabelService.initialize({
98
+ * logoUrl: '/Assets/logos/acme-logo.svg',
99
+ * logoMarkUrl: '/Assets/logos/acme-mark.svg',
100
+ * accent: '#2563eb',
101
+ * onAccent: '#ffffff',
102
+ * headerSurface: '#1e40af',
103
+ * headerContent: '#ffffff',
104
+ * organizationName: 'Acme Corp'
105
+ * });
106
+ * ```
107
+ *
108
+ * Load from API:
109
+ * ```typescript
110
+ * await whitelabelService.loadFromApi('acme-corp');
111
+ * ```
112
+ */
113
+ @Injectable({
114
+ providedIn: 'root',
115
+ })
116
+ export class WhitelabelService {
117
+ private _config = signal<WhitelabelConfig>(DEFAULT_CONFIG);
118
+
119
+ // Readonly computed signals for accessing config values
120
+ readonly logoUrl = computed(() => this._config().logoUrl);
121
+ readonly logoMarkUrl = computed(() => this._config().logoMarkUrl);
122
+ readonly logoAlt = computed(() => this._config().logoAlt);
123
+ readonly logoSize = computed(() => this._config().logoSize);
124
+ readonly logoHeight = computed(() => {
125
+ const customHeight = this._config().logoHeight;
126
+ const size = this._config().logoSize;
127
+
128
+ // If custom height is explicitly specified, always use it
129
+ if (customHeight !== undefined) {
130
+ return customHeight;
131
+ }
132
+
133
+ // Otherwise calculate height based on logoSize
134
+ switch (size) {
135
+ case 'sm':
136
+ return 24;
137
+ case 'md':
138
+ return 28;
139
+ case 'lg':
140
+ return 32;
141
+ case 'xl':
142
+ return 36;
143
+ default:
144
+ return 28;
145
+ }
146
+ });
147
+ readonly appIconSurface = computed(() => this._config().appIconSurface);
148
+ readonly appIconContent = computed(() => this._config().appIconContent);
149
+ readonly accent = computed(() => this._config().accent);
150
+ readonly onAccent = computed(() => this._config().onAccent);
151
+ readonly headerSurface = computed(() => this._config().headerSurface);
152
+ readonly headerContent = computed(() => this._config().headerContent);
153
+ readonly headerAccent = computed(() => this._config().headerAccent);
154
+ readonly onHeaderAccent = computed(() => this._config().onHeaderAccent);
155
+ readonly showCityIllustration = computed(() => this._config().showCityIllustration);
156
+ readonly signInBgType = computed(() => this._config().signInBgType);
157
+ readonly signInBgSolid = computed(() => this._config().signInBgSolid);
158
+ readonly signInBgGradientStart = computed(() => this._config().signInBgGradientStart);
159
+ readonly signInBgGradientEnd = computed(() => this._config().signInBgGradientEnd);
160
+ readonly signInContentColor = computed(() => this._config().signInContentColor);
161
+ readonly organizationName = computed(() => this._config().organizationName);
162
+ readonly organizationId = computed(() => this._config().organizationId);
163
+
164
+ // Computed background style for sign-in page
165
+ readonly signInBgStyle = computed(() => {
166
+ const config = this._config();
167
+ if (config.signInBgType === 'solid') {
168
+ return config.signInBgSolid;
169
+ } else {
170
+ return `linear-gradient(180deg, ${config.signInBgGradientStart} 0%, ${config.signInBgGradientEnd} 100%)`;
171
+ }
172
+ });
173
+
174
+ // Full config accessor
175
+ readonly config = this._config.asReadonly();
176
+
177
+ constructor() {
178
+ // Apply default colors on initialization
179
+ this.applyColors(DEFAULT_CONFIG);
180
+
181
+ // Watch for config changes and update CSS custom properties
182
+ effect(() => {
183
+ const config = this._config();
184
+ this.applyColors(config);
185
+ });
186
+
187
+ // Listen for modal dismissals and re-apply status bar
188
+ // This fixes the issue where Ionic restores cached status bar state
189
+ this.setupModalDismissListener();
190
+ }
191
+
192
+ /**
193
+ * Setup global listener for modal dismiss events
194
+ * Re-applies status bar after modals close to override Ionic's cached state restoration
195
+ */
196
+ private setupModalDismissListener(): void {
197
+ if (typeof document === 'undefined') return;
198
+
199
+ document.addEventListener('ionModalDidDismiss', () => {
200
+ // Small delay to let Ionic's restoration happen first
201
+ setTimeout(() => {
202
+ // Check if we're on the sign-in page
203
+ const isOnSignInPage = document.querySelector('app-sign-in') !== null;
204
+
205
+ if (isOnSignInPage) {
206
+ // On sign-in page - use sign-in background color
207
+ const config = this._config();
208
+ const backgroundColor = config.signInBgType === 'gradient' ? config.signInBgGradientStart : config.signInBgSolid;
209
+ const style = this.getStatusBarStyleForColor(backgroundColor);
210
+
211
+ StatusBar.setBackgroundColor({ color: backgroundColor }).catch(() => {});
212
+ StatusBar.setStyle({ style }).catch(() => {});
213
+ } else {
214
+ // On regular page - use header color
215
+ const config = this._config();
216
+ const headerColor = config.headerSurface;
217
+ const style = this.getStatusBarStyleForColor(headerColor);
218
+
219
+ StatusBar.setStyle({ style }).catch(() => {});
220
+ }
221
+ }, 50);
222
+ });
223
+ }
224
+
225
+ /**
226
+ * Initialize whitelabel configuration
227
+ * Call this early in app initialization (app.config.ts or app.component.ts)
228
+ */
229
+ initialize(config: Partial<WhitelabelConfig>) {
230
+ this._config.update((current) => ({
231
+ ...current,
232
+ ...config,
233
+ }));
234
+ }
235
+
236
+ /**
237
+ * Load whitelabel config from API
238
+ * Typically called on app startup based on subdomain, user tenant, etc.
239
+ *
240
+ * @param organizationId - The organization identifier (subdomain, tenant ID, etc.)
241
+ */
242
+ async loadFromApi(organizationId?: string): Promise<void> {
243
+ try {
244
+ // Example API call structure
245
+ // const response = await fetch(`/api/whitelabel/${organizationId || 'default'}`);
246
+ // const config = await response.json();
247
+ // this.initialize(config);
248
+
249
+ //console.log('Loading whitelabel config from API for:', organizationId);
250
+
251
+ // Example: Different configs for different organizations
252
+ if (organizationId === 'demo-client') {
253
+ this.initialize({
254
+ logoUrl: '/Assets/logos/demo-logo.svg',
255
+ logoMarkUrl: '/Assets/logos/demo-mark.svg',
256
+ logoAlt: 'Demo Client',
257
+ appIconSurface: '#2563eb',
258
+ appIconContent: '#FFFFFF',
259
+ accent: '#2563eb',
260
+ onAccent: '#FFFFFF',
261
+ headerSurface: '#1e40af',
262
+ headerContent: '#FFFFFF',
263
+ headerAccent: 'rgba(255, 255, 255, 0.15)',
264
+ onHeaderAccent: '#FFFFFF',
265
+ organizationName: 'Demo Client',
266
+ organizationId: 'demo-client',
267
+ });
268
+ } else if (organizationId === 'cobblestone') {
269
+ this.initialize({
270
+ logoUrl: '/Assets/logos/cobblestone-logo.svg',
271
+ logoMarkUrl: '/Assets/logos/cobblestone-logomark.svg',
272
+ logoAlt: 'Cobblestone',
273
+ logoSize: 'sm',
274
+ appIconSurface: '#2C3E50',
275
+ appIconContent: '#FFFFFF',
276
+ accent: '#3498DB',
277
+ onAccent: '#FFFFFF',
278
+ headerSurface: '#2C3E50',
279
+ headerContent: '#FFFFFF',
280
+ headerAccent: '#3498DB',
281
+ onHeaderAccent: '#FFFFFF',
282
+ showCityIllustration: false,
283
+ signInBgType: 'gradient',
284
+ signInBgSolid: '#E8EEF2',
285
+ signInBgGradientStart: '#E8EEF2',
286
+ signInBgGradientEnd: '#BDC3C7',
287
+ signInContentColor: '#1a1a1a',
288
+ organizationName: 'Cobblestone',
289
+ organizationId: 'cobblestone',
290
+ });
291
+ }
292
+ // Add more organization-specific configs as needed
293
+ } catch (error) {
294
+ console.error('Failed to load whitelabel config:', error);
295
+ // Fallback to defaults already set
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Update config dynamically (e.g., when user switches organizations)
301
+ */
302
+ updateConfig(updates: Partial<WhitelabelConfig>) {
303
+ this.initialize(updates);
304
+ }
305
+
306
+ /**
307
+ * Update only the brand colors
308
+ */
309
+ updateColors(colors: {
310
+ appIconSurface?: string;
311
+ appIconContent?: string;
312
+ accent?: string;
313
+ onAccent?: string;
314
+ headerSurface?: string;
315
+ headerContent?: string;
316
+ headerAccent?: string;
317
+ onHeaderAccent?: string;
318
+ }) {
319
+ this._config.update((current) => ({
320
+ ...current,
321
+ ...colors,
322
+ }));
323
+ }
324
+
325
+ /**
326
+ * Reset to default configuration
327
+ */
328
+ resetToDefault() {
329
+ this._config.set(DEFAULT_CONFIG);
330
+ }
331
+
332
+ /**
333
+ * Convert hex color to RGB values
334
+ */
335
+ private hexToRgb(hex: string): { r: number; g: number; b: number } | null {
336
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
337
+ return result
338
+ ? {
339
+ r: parseInt(result[1], 16),
340
+ g: parseInt(result[2], 16),
341
+ b: parseInt(result[3], 16),
342
+ }
343
+ : null;
344
+ }
345
+
346
+ /**
347
+ * Calculate relative luminance of a color (WCAG standard)
348
+ * Returns a value between 0 (darkest) and 1 (lightest)
349
+ */
350
+ private getRelativeLuminance(color: string): number {
351
+ const rgb = this.hexToRgb(color);
352
+ if (!rgb) return 0;
353
+
354
+ const rsRGB = rgb.r / 255;
355
+ const gsRGB = rgb.g / 255;
356
+ const bsRGB = rgb.b / 255;
357
+
358
+ const r = rsRGB <= 0.03928 ? rsRGB / 12.92 : Math.pow((rsRGB + 0.055) / 1.055, 2.4);
359
+ const g = gsRGB <= 0.03928 ? gsRGB / 12.92 : Math.pow((gsRGB + 0.055) / 1.055, 2.4);
360
+ const b = bsRGB <= 0.03928 ? bsRGB / 12.92 : Math.pow((bsRGB + 0.055) / 1.055, 2.4);
361
+
362
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
363
+ }
364
+
365
+ /**
366
+ * Determine if a color is light (needs dark status bar content)
367
+ */
368
+ private isColorLight(color: string): boolean {
369
+ const luminance = this.getRelativeLuminance(color);
370
+ return luminance > 0.5;
371
+ }
372
+
373
+ /**
374
+ * Get the appropriate status bar style for a background color
375
+ * Public method for use by sign-in page
376
+ */
377
+ getStatusBarStyleForColor(color: string): Style {
378
+ // Style.Dark = black icons (for light backgrounds)
379
+ // Style.Light = white icons (for dark backgrounds)
380
+ return this.isColorLight(color) ? Style.Light : Style.Dark;
381
+ }
382
+
383
+ /**
384
+ * Generate a hover state color by applying a black overlay
385
+ * This simulates the effect of overlaying #000000 with 10% opacity
386
+ *
387
+ * @param baseColor - Hex color to overlay on (e.g., '#6B5FF5')
388
+ * @param overlayColor - Overlay color (default: '#000000' for darkening)
389
+ * @param overlayAlpha - Opacity of overlay (0-1, default: 0.1 = 10%)
390
+ * @returns Hex color with overlay applied
391
+ *
392
+ * @example
393
+ * generateHoverColor('#6B5FF5') // Returns darker purple for hover state
394
+ * generateHoverColor('#FF0000', '#FFFFFF', 0.2) // Lighten red by 20%
395
+ */
396
+ private generateHoverColor(baseColor: string, overlayColor: string = '#000000', overlayAlpha: number = 0.1): string {
397
+ const base = this.hexToRgb(baseColor);
398
+ const overlay = this.hexToRgb(overlayColor);
399
+
400
+ if (!base || !overlay) return baseColor;
401
+
402
+ // Alpha blending formula: result = overlay * alpha + base * (1 - alpha)
403
+ const r = Math.round(overlay.r * overlayAlpha + base.r * (1 - overlayAlpha));
404
+ const g = Math.round(overlay.g * overlayAlpha + base.g * (1 - overlayAlpha));
405
+ const b = Math.round(overlay.b * overlayAlpha + base.b * (1 - overlayAlpha));
406
+
407
+ // Convert to hex and ensure 2-digit format
408
+ const toHex = (n: number) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, '0');
409
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
410
+ }
411
+
412
+ /**
413
+ * Generate an active/pressed state color (darker than hover)
414
+ * Uses 20% black overlay for a more pronounced pressed effect
415
+ *
416
+ * @param baseColor - Hex color to overlay on
417
+ * @returns Hex color for active/pressed state
418
+ */
419
+ private generateActiveColor(baseColor: string): string {
420
+ return this.generateHoverColor(baseColor, '#000000', 0.2); // 20% darker
421
+ }
422
+
423
+ /**
424
+ * Apply colors to CSS custom properties and native StatusBar
425
+ * This updates the actual CSS variables used throughout the app
426
+ * and the native status bar color on mobile devices
427
+ */
428
+ private applyColors(config: WhitelabelConfig) {
429
+ if (typeof document !== 'undefined') {
430
+ const { appIconSurface, appIconContent, accent, onAccent, headerSurface, headerContent, headerAccent, onHeaderAccent, signInContentColor } = config;
431
+ const root = document.documentElement;
432
+ const body = document.body;
433
+ const ionApp = document.querySelector('ion-app');
434
+
435
+ // Generate hover and active state colors for accent
436
+ const accentHover = this.generateHoverColor(accent);
437
+ const accentActive = this.generateActiveColor(accent);
438
+ const headerAccentHover = this.generateHoverColor(headerAccent);
439
+ const headerAccentActive = this.generateActiveColor(headerAccent);
440
+
441
+ // Generate RGB values for Ionic color system
442
+ const accentRgb = this.hexToRgb(accent);
443
+ const accentRgbString = accentRgb ? `${accentRgb.r}, ${accentRgb.g}, ${accentRgb.b}` : '107, 95, 245';
444
+ const onAccentRgb = this.hexToRgb(onAccent);
445
+ const onAccentRgbString = onAccentRgb ? `${onAccentRgb.r}, ${onAccentRgb.g}, ${onAccentRgb.b}` : '255, 255, 255';
446
+
447
+ // ============================================
448
+ // APP ICON COLORS
449
+ // ============================================
450
+ root.style.setProperty('--color-app-icon-surface', appIconSurface);
451
+ root.style.setProperty('--color-app-icon-content', appIconContent);
452
+ body.style.setProperty('--color-app-icon-surface', appIconSurface);
453
+ body.style.setProperty('--color-app-icon-content', appIconContent);
454
+
455
+ // ============================================
456
+ // ACCENT COLORS (buttons, FABs, active tabs, selections)
457
+ // ============================================
458
+
459
+ // Base color
460
+ root.style.setProperty('--color-accent', accent);
461
+ root.style.setProperty('--color-on-accent', onAccent);
462
+ body.style.setProperty('--color-accent', accent);
463
+ body.style.setProperty('--color-on-accent', onAccent);
464
+
465
+ // Hover state (10% darker)
466
+ root.style.setProperty('--color-accent-hover', accentHover);
467
+ body.style.setProperty('--color-accent-hover', accentHover);
468
+
469
+ // Active/pressed state (20% darker)
470
+ root.style.setProperty('--color-accent-active', accentActive);
471
+ body.style.setProperty('--color-accent-active', accentActive);
472
+
473
+ // Legacy aliases for backward compatibility
474
+ root.style.setProperty('--color-background-brand', accent);
475
+ root.style.setProperty('--color-brand-base', accent);
476
+ root.style.setProperty('--color-primary-surface', accent);
477
+ root.style.setProperty('--color-primary-content', onAccent);
478
+ root.style.setProperty('--color-brand-base-hover', accentHover);
479
+ root.style.setProperty('--color-primary-surface-hover', accentHover);
480
+ root.style.setProperty('--color-brand-base-active', accentActive);
481
+ root.style.setProperty('--color-primary-surface-active', accentActive);
482
+
483
+ body.style.setProperty('--color-background-brand', accent);
484
+ body.style.setProperty('--color-brand-base', accent);
485
+ body.style.setProperty('--color-primary-surface', accent);
486
+ body.style.setProperty('--color-primary-content', onAccent);
487
+ body.style.setProperty('--color-brand-base-hover', accentHover);
488
+ body.style.setProperty('--color-primary-surface-hover', accentHover);
489
+ body.style.setProperty('--color-brand-base-active', accentActive);
490
+ body.style.setProperty('--color-primary-surface-active', accentActive);
491
+
492
+ if (ionApp) {
493
+ (ionApp as HTMLElement).style.setProperty('--color-accent', accent);
494
+ (ionApp as HTMLElement).style.setProperty('--color-on-accent', onAccent);
495
+ (ionApp as HTMLElement).style.setProperty('--color-accent-hover', accentHover);
496
+ (ionApp as HTMLElement).style.setProperty('--color-accent-active', accentActive);
497
+ (ionApp as HTMLElement).style.setProperty('--color-background-brand', accent);
498
+ (ionApp as HTMLElement).style.setProperty('--color-brand-base', accent);
499
+ (ionApp as HTMLElement).style.setProperty('--color-primary-surface', accent);
500
+ (ionApp as HTMLElement).style.setProperty('--color-primary-content', onAccent);
501
+ (ionApp as HTMLElement).style.setProperty('--color-brand-base-hover', accentHover);
502
+ (ionApp as HTMLElement).style.setProperty('--color-primary-surface-hover', accentHover);
503
+ (ionApp as HTMLElement).style.setProperty('--color-brand-base-active', accentActive);
504
+ (ionApp as HTMLElement).style.setProperty('--color-primary-surface-active', accentActive);
505
+ (ionApp as HTMLElement).style.setProperty('--color-selected', accent);
506
+ }
507
+
508
+ // Update tab button selected color directly
509
+ // CSS variable inheritance doesn't always work dynamically with Ionic components
510
+ document.querySelectorAll('ion-tab-button').forEach((tabButton) => {
511
+ (tabButton as HTMLElement).style.setProperty('--color-selected', accent);
512
+ });
513
+
514
+ // ============================================
515
+ // IONIC COLOR SYSTEM
516
+ // ============================================
517
+ // Update Ionic's primary color system with RGB values
518
+ root.style.setProperty('--ion-color-primary', accent);
519
+ root.style.setProperty('--ion-color-primary-rgb', accentRgbString);
520
+ root.style.setProperty('--ion-color-primary-contrast', onAccent);
521
+ root.style.setProperty('--ion-color-primary-contrast-rgb', onAccentRgbString);
522
+ root.style.setProperty('--ion-color-primary-shade', accentHover);
523
+ root.style.setProperty('--ion-color-primary-tint', accent);
524
+
525
+ body.style.setProperty('--ion-color-primary', accent);
526
+ body.style.setProperty('--ion-color-primary-rgb', accentRgbString);
527
+ body.style.setProperty('--ion-color-primary-contrast', onAccent);
528
+ body.style.setProperty('--ion-color-primary-contrast-rgb', onAccentRgbString);
529
+ body.style.setProperty('--ion-color-primary-shade', accentHover);
530
+ body.style.setProperty('--ion-color-primary-tint', accent);
531
+
532
+ if (ionApp) {
533
+ (ionApp as HTMLElement).style.setProperty('--ion-color-primary', accent);
534
+ (ionApp as HTMLElement).style.setProperty('--ion-color-primary-rgb', accentRgbString);
535
+ (ionApp as HTMLElement).style.setProperty('--ion-color-primary-contrast', onAccent);
536
+ (ionApp as HTMLElement).style.setProperty('--ion-color-primary-contrast-rgb', onAccentRgbString);
537
+ (ionApp as HTMLElement).style.setProperty('--ion-color-primary-shade', accentHover);
538
+ (ionApp as HTMLElement).style.setProperty('--ion-color-primary-tint', accent);
539
+ }
540
+
541
+ // ============================================
542
+ // HEADER COLORS (navigation bar, header-expandable)
543
+ // ============================================
544
+
545
+ // Base colors
546
+ root.style.setProperty('--color-header-surface', headerSurface);
547
+ root.style.setProperty('--color-header-content', headerContent);
548
+ root.style.setProperty('--color-header-accent', headerAccent);
549
+ root.style.setProperty('--color-on-header-accent', onHeaderAccent);
550
+ body.style.setProperty('--color-header-surface', headerSurface);
551
+ body.style.setProperty('--color-header-content', headerContent);
552
+ body.style.setProperty('--color-header-accent', headerAccent);
553
+ body.style.setProperty('--color-on-header-accent', onHeaderAccent);
554
+
555
+ // Hover/active states for header accent
556
+ root.style.setProperty('--color-header-accent-hover', headerAccentHover);
557
+ root.style.setProperty('--color-header-accent-active', headerAccentActive);
558
+ body.style.setProperty('--color-header-accent-hover', headerAccentHover);
559
+ body.style.setProperty('--color-header-accent-active', headerAccentActive);
560
+
561
+ // Legacy aliases for backward compatibility
562
+ root.style.setProperty('--color-brand-secondary', headerSurface);
563
+ root.style.setProperty('--color-secondary-surface', headerSurface);
564
+ root.style.setProperty('--color-secondary-content', headerContent);
565
+ root.style.setProperty('--header-content-color', headerContent);
566
+ root.style.setProperty('--color-brand-secondary-hover', this.generateHoverColor(headerSurface));
567
+ root.style.setProperty('--color-secondary-surface-hover', this.generateHoverColor(headerSurface));
568
+ root.style.setProperty('--color-brand-secondary-active', this.generateActiveColor(headerSurface));
569
+ root.style.setProperty('--color-secondary-surface-active', this.generateActiveColor(headerSurface));
570
+
571
+ body.style.setProperty('--color-brand-secondary', headerSurface);
572
+ body.style.setProperty('--color-secondary-surface', headerSurface);
573
+ body.style.setProperty('--color-secondary-content', headerContent);
574
+ body.style.setProperty('--header-content-color', headerContent);
575
+ body.style.setProperty('--color-brand-secondary-hover', this.generateHoverColor(headerSurface));
576
+ body.style.setProperty('--color-secondary-surface-hover', this.generateHoverColor(headerSurface));
577
+ body.style.setProperty('--color-brand-secondary-active', this.generateActiveColor(headerSurface));
578
+ body.style.setProperty('--color-secondary-surface-active', this.generateActiveColor(headerSurface));
579
+
580
+ if (ionApp) {
581
+ (ionApp as HTMLElement).style.setProperty('--color-header-surface', headerSurface);
582
+ (ionApp as HTMLElement).style.setProperty('--color-header-content', headerContent);
583
+ (ionApp as HTMLElement).style.setProperty('--color-header-accent', headerAccent);
584
+ (ionApp as HTMLElement).style.setProperty('--color-on-header-accent', onHeaderAccent);
585
+ (ionApp as HTMLElement).style.setProperty('--color-header-accent-hover', headerAccentHover);
586
+ (ionApp as HTMLElement).style.setProperty('--color-header-accent-active', headerAccentActive);
587
+ (ionApp as HTMLElement).style.setProperty('--color-brand-secondary', headerSurface);
588
+ (ionApp as HTMLElement).style.setProperty('--color-secondary-surface', headerSurface);
589
+ (ionApp as HTMLElement).style.setProperty('--color-secondary-content', headerContent);
590
+ (ionApp as HTMLElement).style.setProperty('--header-content-color', headerContent);
591
+ (ionApp as HTMLElement).style.setProperty('--color-brand-secondary-hover', this.generateHoverColor(headerSurface));
592
+ (ionApp as HTMLElement).style.setProperty('--color-secondary-surface-hover', this.generateHoverColor(headerSurface));
593
+ (ionApp as HTMLElement).style.setProperty('--color-brand-secondary-active', this.generateActiveColor(headerSurface));
594
+ (ionApp as HTMLElement).style.setProperty('--color-secondary-surface-active', this.generateActiveColor(headerSurface));
595
+ }
596
+
597
+ // Also set RGB values for use with rgba() opacity variations
598
+ const rgbContent = this.hexToRgb(headerContent);
599
+ if (rgbContent) {
600
+ const rgbValue = `${rgbContent.r}, ${rgbContent.g}, ${rgbContent.b}`;
601
+ root.style.setProperty('--header-content-color-rgb', rgbValue);
602
+ root.style.setProperty('--color-secondary-content-rgb', rgbValue);
603
+ root.style.setProperty('--color-header-content-rgb', rgbValue);
604
+ body.style.setProperty('--header-content-color-rgb', rgbValue);
605
+ body.style.setProperty('--color-secondary-content-rgb', rgbValue);
606
+ body.style.setProperty('--color-header-content-rgb', rgbValue);
607
+ if (ionApp) {
608
+ (ionApp as HTMLElement).style.setProperty('--header-content-color-rgb', rgbValue);
609
+ (ionApp as HTMLElement).style.setProperty('--color-secondary-content-rgb', rgbValue);
610
+ (ionApp as HTMLElement).style.setProperty('--color-header-content-rgb', rgbValue);
611
+ }
612
+ }
613
+
614
+ const rgbOnHeaderAccent = this.hexToRgb(onHeaderAccent);
615
+ if (rgbOnHeaderAccent) {
616
+ const rgbValue = `${rgbOnHeaderAccent.r}, ${rgbOnHeaderAccent.g}, ${rgbOnHeaderAccent.b}`;
617
+ root.style.setProperty('--color-on-header-accent-rgb', rgbValue);
618
+ body.style.setProperty('--color-on-header-accent-rgb', rgbValue);
619
+ if (ionApp) {
620
+ (ionApp as HTMLElement).style.setProperty('--color-on-header-accent-rgb', rgbValue);
621
+ }
622
+ }
623
+
624
+ // Update theme-color meta tag for browser chrome/status bar (PWA/iOS)
625
+ const metaThemeColor = document.querySelector('meta[name="theme-color"]');
626
+ if (metaThemeColor) {
627
+ metaThemeColor.setAttribute('content', headerSurface);
628
+ }
629
+
630
+ // ============================================
631
+ // SIGN-IN PAGE COLORS
632
+ // ============================================
633
+ root.style.setProperty('--color-signin-content', signInContentColor);
634
+ body.style.setProperty('--color-signin-content', signInContentColor);
635
+ if (ionApp) {
636
+ (ionApp as HTMLElement).style.setProperty('--color-signin-content', signInContentColor);
637
+ }
638
+
639
+ // Update native StatusBar color (Capacitor - Android only, iOS ignores this)
640
+ this.updateNativeStatusBar(headerSurface);
641
+
642
+ // console.log('Applied whitelabel colors:', {
643
+ // appIconSurface,
644
+ // appIconContent,
645
+ // accent,
646
+ // accentHover,
647
+ // accentActive,
648
+ // onAccent,
649
+ // headerSurface,
650
+ // headerContent,
651
+ // headerAccent,
652
+ // headerAccentHover,
653
+ // headerAccentActive,
654
+ // onHeaderAccent,
655
+ // signInContentColor
656
+ // });
657
+ }
658
+ }
659
+
660
+ /**
661
+ * Update the native status bar color AND style
662
+ * Sets background color (Android) and content style (iOS/Android)
663
+ */
664
+ private async updateNativeStatusBar(color: string): Promise<void> {
665
+ try {
666
+ await StatusBar.setBackgroundColor({ color });
667
+
668
+ // Calculate and set appropriate style for status bar content
669
+ const style = this.getStatusBarStyleForColor(color);
670
+ await StatusBar.setStyle({ style });
671
+ } catch (e) {
672
+ // StatusBar API not available (web browser) or failed
673
+ }
674
+ }
675
+ }