tin-spa 20.6.9 → 20.7.0
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.
- package/fesm2022/tin-spa.mjs +168 -62
- package/fesm2022/tin-spa.mjs.map +1 -1
- package/index.d.ts +37 -7
- package/package.json +4 -2
package/fesm2022/tin-spa.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, Inject, Component, InjectionToken, Optional, inject, Input, Directive, NgModule, EventEmitter, Output, forwardRef, HostListener, ViewChild, Pipe, ViewEncapsulation, ContentChild } from '@angular/core';
|
|
2
|
+
import { Injectable, Inject, Component, InjectionToken, makeEnvironmentProviders, Optional, inject, Input, Directive, NgModule, EventEmitter, Output, forwardRef, HostListener, ViewChild, Pipe, ViewEncapsulation, ContentChild } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/material/dialog';
|
|
4
4
|
import { MAT_DIALOG_DATA, MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS, MatDialog } from '@angular/material/dialog';
|
|
5
5
|
import * as i2 from '@angular/common';
|
|
6
|
-
import {
|
|
6
|
+
import { PathLocationStrategy, HashLocationStrategy, PlatformLocation, LocationStrategy, APP_BASE_HREF, CommonModule, DecimalPipe, CurrencyPipe, DatePipe } from '@angular/common';
|
|
7
7
|
import * as i5 from '@angular/material/button';
|
|
8
8
|
import { MatButtonModule } from '@angular/material/button';
|
|
9
9
|
import * as i4 from '@angular/material/icon';
|
|
@@ -21,6 +21,9 @@ import { SocialLoginModule } from '@abacritt/angularx-social-login';
|
|
|
21
21
|
import * as i1$1 from '@angular/common/http';
|
|
22
22
|
import { HttpHeaders, HttpClientModule, HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
|
|
23
23
|
import * as signalR from '@microsoft/signalr';
|
|
24
|
+
import * as i10 from '@azure/msal-angular';
|
|
25
|
+
import { MSAL_INSTANCE, MsalService } from '@azure/msal-angular';
|
|
26
|
+
import { PublicClientApplication, BrowserCacheLocation } from '@azure/msal-browser';
|
|
24
27
|
import * as i1$3 from '@angular/cdk/layout';
|
|
25
28
|
import * as i1$4 from '@angular/service-worker';
|
|
26
29
|
import * as i12 from '@angular/material/autocomplete';
|
|
@@ -79,7 +82,6 @@ import { moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk
|
|
|
79
82
|
import * as i6$2 from 'ngx-doc-viewer';
|
|
80
83
|
import { NgxDocViewerModule } from 'ngx-doc-viewer';
|
|
81
84
|
import * as i1$5 from '@angular/platform-browser';
|
|
82
|
-
import * as i10 from '@azure/msal-angular';
|
|
83
85
|
|
|
84
86
|
class TinSpaService {
|
|
85
87
|
constructor() { }
|
|
@@ -1160,6 +1162,7 @@ class AppConfig {
|
|
|
1160
1162
|
this.navWidth = '200px';
|
|
1161
1163
|
this.navColor = 'rgba(0,0,0,0.78)'; // Changed: Default dark overlay for side-modern sidebar
|
|
1162
1164
|
this.navImage = ''; // Changed: Default no background image for side-modern sidebar
|
|
1165
|
+
this.keepSignedIn = false; // Changed: Hide "Keep me signed in" unless explicitly enabled
|
|
1163
1166
|
}
|
|
1164
1167
|
}
|
|
1165
1168
|
|
|
@@ -1487,9 +1490,95 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
1487
1490
|
}]
|
|
1488
1491
|
}], ctorParameters: () => [] });
|
|
1489
1492
|
|
|
1493
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
1494
|
+
// tin-spa runtime configuration
|
|
1495
|
+
//
|
|
1496
|
+
// Centralises the "which runtime behaviours does this app want" decisions that used to be
|
|
1497
|
+
// scattered across each consuming app (routing strategy, real-time/SignalR, Microsoft SSO).
|
|
1498
|
+
//
|
|
1499
|
+
// A consuming app declares intent in ONE place:
|
|
1500
|
+
//
|
|
1501
|
+
// providers: [
|
|
1502
|
+
// provideTinSpaRuntime({
|
|
1503
|
+
// pathRouting: false, // hash routing (default) vs path routing
|
|
1504
|
+
// realTime: false, // SignalR on/off
|
|
1505
|
+
// microsoftAuth: false, // MSAL on/off (also requires HTTPS)
|
|
1506
|
+
// msal: { clientId, authority, redirectUri }, // only if microsoftAuth is used
|
|
1507
|
+
// }),
|
|
1508
|
+
// ]
|
|
1509
|
+
//
|
|
1510
|
+
// The config is the single source of truth and is read live at startup — nothing is cached in
|
|
1511
|
+
// localStorage. So when you change a flag here and redeploy, every user picks up the new value on
|
|
1512
|
+
// their next load. To change a flag, edit the call above and rebuild.
|
|
1513
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
1514
|
+
const DEFAULTS = {
|
|
1515
|
+
pathRouting: false,
|
|
1516
|
+
realTime: false,
|
|
1517
|
+
microsoftAuth: false,
|
|
1518
|
+
};
|
|
1519
|
+
const TIN_SPA_RUNTIME_CONFIG = new InjectionToken('TIN_SPA_RUNTIME_CONFIG', {
|
|
1520
|
+
factory: () => DEFAULTS, // safe fallback if an app forgets to call provideTinSpaRuntime()
|
|
1521
|
+
});
|
|
1522
|
+
// ── DI factories ────────────────────────────────────────────────────────────
|
|
1523
|
+
function tinSpaLocationStrategyFactory(platformLocation, baseHref, config) {
|
|
1524
|
+
return config.pathRouting
|
|
1525
|
+
? new PathLocationStrategy(platformLocation, baseHref)
|
|
1526
|
+
: new HashLocationStrategy(platformLocation, baseHref);
|
|
1527
|
+
}
|
|
1528
|
+
function tinSpaMsalInstanceFactory(config) {
|
|
1529
|
+
const wantsMsal = !!config.microsoftAuth;
|
|
1530
|
+
const secure = typeof window === 'undefined' || window.isSecureContext;
|
|
1531
|
+
if (wantsMsal && !config.msal) {
|
|
1532
|
+
console.warn('[tin-spa] microsoftAuth is enabled but no msal credentials were provided to provideTinSpaRuntime(). Microsoft SSO is INACTIVE.');
|
|
1533
|
+
}
|
|
1534
|
+
if (wantsMsal && !secure) {
|
|
1535
|
+
console.warn('[tin-spa] microsoftAuth is enabled but the app is NOT running over HTTPS (secure context). Microsoft SSO is INACTIVE until served over HTTPS.');
|
|
1536
|
+
}
|
|
1537
|
+
const active = wantsMsal && !!config.msal && secure;
|
|
1538
|
+
if (!active) {
|
|
1539
|
+
// Stub: MsalService's constructor calls instance.initializeWrapperLibrary(), so it must exist
|
|
1540
|
+
// or the app crashes at bootstrap. loginPopup rejects so callers' error handlers report it.
|
|
1541
|
+
return {
|
|
1542
|
+
initializeWrapperLibrary: () => { },
|
|
1543
|
+
initialize: () => Promise.resolve(),
|
|
1544
|
+
getAllAccounts: () => [],
|
|
1545
|
+
getActiveAccount: () => null,
|
|
1546
|
+
loginPopup: () => Promise.reject(new Error(!wantsMsal ? 'Microsoft login is disabled in app config.'
|
|
1547
|
+
: !config.msal ? 'Microsoft login is not configured (missing msal credentials).'
|
|
1548
|
+
: 'Microsoft login requires HTTPS (a secure context).')),
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
return new PublicClientApplication({
|
|
1552
|
+
auth: { ...config.msal },
|
|
1553
|
+
cache: { cacheLocation: BrowserCacheLocation.LocalStorage, storeAuthStateInCookie: false },
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
1557
|
+
/**
|
|
1558
|
+
* Wires up tin-spa runtime behaviours (routing strategy, real-time, MSAL) from a single config.
|
|
1559
|
+
* Add the result to your AppModule (or bootstrap) providers.
|
|
1560
|
+
*/
|
|
1561
|
+
function provideTinSpaRuntime(config) {
|
|
1562
|
+
const merged = { ...DEFAULTS, ...config };
|
|
1563
|
+
return makeEnvironmentProviders([
|
|
1564
|
+
{ provide: TIN_SPA_RUNTIME_CONFIG, useValue: merged },
|
|
1565
|
+
{
|
|
1566
|
+
provide: LocationStrategy,
|
|
1567
|
+
useFactory: tinSpaLocationStrategyFactory,
|
|
1568
|
+
deps: [PlatformLocation, [new Optional(), new Inject(APP_BASE_HREF)], TIN_SPA_RUNTIME_CONFIG],
|
|
1569
|
+
},
|
|
1570
|
+
// MSAL_INSTANCE is built lazily (when MsalService is first injected, i.e. on the login page),
|
|
1571
|
+
// so it reads the config token rather than running at bootstrap.
|
|
1572
|
+
{ provide: MSAL_INSTANCE, useFactory: tinSpaMsalInstanceFactory, deps: [TIN_SPA_RUNTIME_CONFIG] },
|
|
1573
|
+
MsalService,
|
|
1574
|
+
]);
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1490
1577
|
// Changed: Consolidated SignalR service — single hub connection for both notifications and entity broadcasts
|
|
1491
1578
|
class SignalRService {
|
|
1492
|
-
|
|
1579
|
+
// Changed: real-time is now driven centrally by tin-spa runtime config (provideTinSpaRuntime).
|
|
1580
|
+
constructor(runtimeConfig) {
|
|
1581
|
+
this.runtimeConfig = runtimeConfig;
|
|
1493
1582
|
this.hubConnection = null;
|
|
1494
1583
|
this.notificationCount = new BehaviorSubject(0);
|
|
1495
1584
|
this.notificationCount$ = this.notificationCount.asObservable();
|
|
@@ -1516,10 +1605,16 @@ class SignalRService {
|
|
|
1516
1605
|
this.dataHubConnected$ = this.dataHubConnected.asObservable();
|
|
1517
1606
|
this.currentToken = ''; // Changed: Live token reference for SignalR reconnection
|
|
1518
1607
|
}
|
|
1608
|
+
/** Effective real-time enablement: legacy E2E kill-switch wins, else the configured runtime flag. */
|
|
1609
|
+
realTimeEnabled() {
|
|
1610
|
+
if (typeof localStorage !== 'undefined' && localStorage.getItem('disableRealTime') === 'true')
|
|
1611
|
+
return false; // legacy/E2E override
|
|
1612
|
+
return this.runtimeConfig ? !!this.runtimeConfig.realTime : false;
|
|
1613
|
+
}
|
|
1519
1614
|
// Changed: Single startConnection method handles both notification and entity change listeners
|
|
1520
1615
|
startConnection(hubUrl, token) {
|
|
1521
|
-
//
|
|
1522
|
-
if (
|
|
1616
|
+
// Real-time disabled (by config or override) -> do not connect.
|
|
1617
|
+
if (!this.realTimeEnabled())
|
|
1523
1618
|
return;
|
|
1524
1619
|
this.currentToken = token; // Changed: Store token so it can be updated after refresh
|
|
1525
1620
|
if (this.hubConnection && this.hubConnection.state !== signalR.HubConnectionState.Disconnected)
|
|
@@ -1593,13 +1688,18 @@ class SignalRService {
|
|
|
1593
1688
|
this.dataHubConnected.next(false); // Changed: Mark disconnected on stop
|
|
1594
1689
|
}
|
|
1595
1690
|
}
|
|
1596
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SignalRService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1691
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SignalRService, deps: [{ token: TIN_SPA_RUNTIME_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1597
1692
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SignalRService, providedIn: 'root' }); }
|
|
1598
1693
|
}
|
|
1599
1694
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SignalRService, decorators: [{
|
|
1600
1695
|
type: Injectable,
|
|
1601
1696
|
args: [{ providedIn: 'root' }]
|
|
1602
|
-
}]
|
|
1697
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1698
|
+
type: Optional
|
|
1699
|
+
}, {
|
|
1700
|
+
type: Inject,
|
|
1701
|
+
args: [TIN_SPA_RUNTIME_CONFIG]
|
|
1702
|
+
}] }] });
|
|
1603
1703
|
|
|
1604
1704
|
class AuthService {
|
|
1605
1705
|
constructor(_route, socialService, storage, router, httpService, messageService, logService, signalRService) {
|
|
@@ -2821,7 +2921,7 @@ class DataServiceLib {
|
|
|
2821
2921
|
this.capAdmin.name = "cap2";
|
|
2822
2922
|
this.capAdmin.display = "Admin";
|
|
2823
2923
|
this.capAdmin.icon = "security";
|
|
2824
|
-
this.capAdmin.capSubItems = [this.capUsers, this.capRoles, this.capLogs, this.capSettings
|
|
2924
|
+
this.capAdmin.capSubItems = [this.capUsers, this.capRoles, this.capLogs, this.capSettings];
|
|
2825
2925
|
this.capUsers.name = "cap3";
|
|
2826
2926
|
this.capUsers.display = "Users";
|
|
2827
2927
|
this.capUsers.link = "home/admin/users";
|
|
@@ -2873,7 +2973,10 @@ class DataServiceLib {
|
|
|
2873
2973
|
this.capBilling.icon = "receipt_long";
|
|
2874
2974
|
this.capGeneral.name = "cap11";
|
|
2875
2975
|
this.capGeneral.display = "General";
|
|
2876
|
-
this.capGeneral.capSubItems = [
|
|
2976
|
+
this.capGeneral.capSubItems = [
|
|
2977
|
+
this.capNotifications, this.capApprovals, this.capCustomers, this.capSuppliers,
|
|
2978
|
+
this.capCategories, this.capSubCategories, this.capBrands, this.capTasks, this.capSubscription, this.capBilling
|
|
2979
|
+
];
|
|
2877
2980
|
this.capCustomers.name = "cap12";
|
|
2878
2981
|
this.capCustomers.display = "Customers";
|
|
2879
2982
|
this.capCustomers.link = "home/general/customers";
|
|
@@ -3456,6 +3559,7 @@ class DialogService {
|
|
|
3456
3559
|
width: config.options?.width ? config.options?.width : (this.smallScreen ? '900px' : (hasTables ? '90%' : '900px')),
|
|
3457
3560
|
height: config.options?.height ? config.options?.height : (hasTables ? '90%' : 'auto'),
|
|
3458
3561
|
maxWidth: '100vw',
|
|
3562
|
+
maxHeight: '92vh', // Changed: cap dialog height so tall auto-height (no-table) forms scroll instead of overflowing off-screen
|
|
3459
3563
|
data: config
|
|
3460
3564
|
};
|
|
3461
3565
|
const dialogRef = this.dialog.open(dialogComponent, dialogOptions);
|
|
@@ -9926,7 +10030,7 @@ class ToastComponent {
|
|
|
9926
10030
|
this.subs.forEach(s => s.unsubscribe());
|
|
9927
10031
|
}
|
|
9928
10032
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ToastComponent, deps: [{ token: SignalRService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9929
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ToastComponent, isStandalone: false, selector: "spa-toast", ngImport: i0, template: "<!-- Changed: Fixed-position toast container, top-right corner with cascading layout -->\n<div class=\"toast-container\">\n <div *ngFor=\"let toast of toasts; trackBy: trackToast\"\n class=\"toast-item\"\n [@toastAnim]\n (click)=\"dismiss(toast.id)\">\n <div class=\"toast-accent\" [style.background-color]=\"toast.color\"></div>\n <mat-icon class=\"toast-icon\" [style.color]=\"toast.color\">{{toast.icon}}</mat-icon>\n <div class=\"toast-content\">\n <span class=\"toast-category\">{{toast.category}}</span>\n <span class=\"toast-message\">{{toast.message}}</span>\n </div>\n <mat-icon class=\"toast-close\">close</mat-icon>\n </div>\n</div>\n", styles: [".toast-container{position:fixed;top:72px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none}.toast-item{display:flex;align-items:center;min-width:280px;max-width:360px;padding:10px 14px;border-radius:8px;background:#fffffff2;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 4px 20px #0000001f,0 1px 4px #00000014;cursor:pointer;pointer-events:auto;overflow:hidden;position:relative}.toast-item:hover{box-shadow:0 6px 24px #00000029}.toast-accent{position:absolute;left:0;top:0;bottom:0;width:4px;border-radius:8px 0 0 8px}.toast-icon{font-size:20px;width:20px;height:20px;margin-left:8px;margin-right:10px;flex-shrink:0}.toast-content{flex:1;display:flex;flex-direction:column;min-width:0}.toast-category{font-size:10px;font-weight:600;text-transform:uppercase;color:#999;letter-spacing:.5px;line-height:1.2}.toast-message{font-size:13px;font-weight:500;color:#333;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast-close{font-size:16px;width:16px;height:16px;color:#bbb;margin-left:8px;flex-shrink:0}.toast-item:hover .toast-close{color:#666}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], animations: [
|
|
10033
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ToastComponent, isStandalone: false, selector: "spa-toast", ngImport: i0, template: "<!-- Changed: Fixed-position toast container, top-right corner with cascading layout -->\r\n<div class=\"toast-container\">\r\n <div *ngFor=\"let toast of toasts; trackBy: trackToast\"\r\n class=\"toast-item\"\r\n [@toastAnim]\r\n (click)=\"dismiss(toast.id)\">\r\n <div class=\"toast-accent\" [style.background-color]=\"toast.color\"></div>\r\n <mat-icon class=\"toast-icon\" [style.color]=\"toast.color\">{{toast.icon}}</mat-icon>\r\n <div class=\"toast-content\">\r\n <span class=\"toast-category\">{{toast.category}}</span>\r\n <span class=\"toast-message\">{{toast.message}}</span>\r\n </div>\r\n <mat-icon class=\"toast-close\">close</mat-icon>\r\n </div>\r\n</div>\r\n", styles: [".toast-container{position:fixed;top:72px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none}.toast-item{display:flex;align-items:center;min-width:280px;max-width:360px;padding:10px 14px;border-radius:8px;background:#fffffff2;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 4px 20px #0000001f,0 1px 4px #00000014;cursor:pointer;pointer-events:auto;overflow:hidden;position:relative}.toast-item:hover{box-shadow:0 6px 24px #00000029}.toast-accent{position:absolute;left:0;top:0;bottom:0;width:4px;border-radius:8px 0 0 8px}.toast-icon{font-size:20px;width:20px;height:20px;margin-left:8px;margin-right:10px;flex-shrink:0}.toast-content{flex:1;display:flex;flex-direction:column;min-width:0}.toast-category{font-size:10px;font-weight:600;text-transform:uppercase;color:#999;letter-spacing:.5px;line-height:1.2}.toast-message{font-size:13px;font-weight:500;color:#333;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast-close{font-size:16px;width:16px;height:16px;color:#bbb;margin-left:8px;flex-shrink:0}.toast-item:hover .toast-close{color:#666}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], animations: [
|
|
9930
10034
|
trigger('toastAnim', [
|
|
9931
10035
|
transition(':enter', [
|
|
9932
10036
|
style({ opacity: 0, transform: 'translateX(100%)' }),
|
|
@@ -9950,7 +10054,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
9950
10054
|
animate('250ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 0, transform: 'translateX(100%)' }))
|
|
9951
10055
|
])
|
|
9952
10056
|
])
|
|
9953
|
-
], template: "<!-- Changed: Fixed-position toast container, top-right corner with cascading layout -->\n<div class=\"toast-container\">\n <div *ngFor=\"let toast of toasts; trackBy: trackToast\"\n class=\"toast-item\"\n [@toastAnim]\n (click)=\"dismiss(toast.id)\">\n <div class=\"toast-accent\" [style.background-color]=\"toast.color\"></div>\n <mat-icon class=\"toast-icon\" [style.color]=\"toast.color\">{{toast.icon}}</mat-icon>\n <div class=\"toast-content\">\n <span class=\"toast-category\">{{toast.category}}</span>\n <span class=\"toast-message\">{{toast.message}}</span>\n </div>\n <mat-icon class=\"toast-close\">close</mat-icon>\n </div>\n</div>\n", styles: [".toast-container{position:fixed;top:72px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none}.toast-item{display:flex;align-items:center;min-width:280px;max-width:360px;padding:10px 14px;border-radius:8px;background:#fffffff2;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 4px 20px #0000001f,0 1px 4px #00000014;cursor:pointer;pointer-events:auto;overflow:hidden;position:relative}.toast-item:hover{box-shadow:0 6px 24px #00000029}.toast-accent{position:absolute;left:0;top:0;bottom:0;width:4px;border-radius:8px 0 0 8px}.toast-icon{font-size:20px;width:20px;height:20px;margin-left:8px;margin-right:10px;flex-shrink:0}.toast-content{flex:1;display:flex;flex-direction:column;min-width:0}.toast-category{font-size:10px;font-weight:600;text-transform:uppercase;color:#999;letter-spacing:.5px;line-height:1.2}.toast-message{font-size:13px;font-weight:500;color:#333;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast-close{font-size:16px;width:16px;height:16px;color:#bbb;margin-left:8px;flex-shrink:0}.toast-item:hover .toast-close{color:#666}\n"] }]
|
|
10057
|
+
], template: "<!-- Changed: Fixed-position toast container, top-right corner with cascading layout -->\r\n<div class=\"toast-container\">\r\n <div *ngFor=\"let toast of toasts; trackBy: trackToast\"\r\n class=\"toast-item\"\r\n [@toastAnim]\r\n (click)=\"dismiss(toast.id)\">\r\n <div class=\"toast-accent\" [style.background-color]=\"toast.color\"></div>\r\n <mat-icon class=\"toast-icon\" [style.color]=\"toast.color\">{{toast.icon}}</mat-icon>\r\n <div class=\"toast-content\">\r\n <span class=\"toast-category\">{{toast.category}}</span>\r\n <span class=\"toast-message\">{{toast.message}}</span>\r\n </div>\r\n <mat-icon class=\"toast-close\">close</mat-icon>\r\n </div>\r\n</div>\r\n", styles: [".toast-container{position:fixed;top:72px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none}.toast-item{display:flex;align-items:center;min-width:280px;max-width:360px;padding:10px 14px;border-radius:8px;background:#fffffff2;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 4px 20px #0000001f,0 1px 4px #00000014;cursor:pointer;pointer-events:auto;overflow:hidden;position:relative}.toast-item:hover{box-shadow:0 6px 24px #00000029}.toast-accent{position:absolute;left:0;top:0;bottom:0;width:4px;border-radius:8px 0 0 8px}.toast-icon{font-size:20px;width:20px;height:20px;margin-left:8px;margin-right:10px;flex-shrink:0}.toast-content{flex:1;display:flex;flex-direction:column;min-width:0}.toast-category{font-size:10px;font-weight:600;text-transform:uppercase;color:#999;letter-spacing:.5px;line-height:1.2}.toast-message{font-size:13px;font-weight:500;color:#333;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast-close{font-size:16px;width:16px;height:16px;color:#bbb;margin-left:8px;flex-shrink:0}.toast-item:hover .toast-close{color:#666}\n"] }]
|
|
9954
10058
|
}], ctorParameters: () => [{ type: SignalRService }] });
|
|
9955
10059
|
|
|
9956
10060
|
// Floating chat widget component for in-app Agent (renamed from AssistantComponent)
|
|
@@ -10059,7 +10163,7 @@ class AgentComponent {
|
|
|
10059
10163
|
}
|
|
10060
10164
|
}
|
|
10061
10165
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AgentComponent, deps: [{ token: AgentService }, { token: SignalRService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10062
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: AgentComponent, isStandalone: false, selector: "spa-agent", host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "messageContainer", first: true, predicate: ["messageContainer"], descendants: true }, { propertyName: "messageInput", first: true, predicate: ["messageInput"], descendants: true }], ngImport: i0, template: "<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\n\n<!-- FAB toggle button -->\n<button mat-fab class=\"agent-fab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\n</button>\n\n<!-- Chat window -->\n<div class=\"agent-window\" *ngIf=\"isOpen\" @slideUp>\n\n <!-- Header with online status -->\n <div class=\"agent-header\">\n <div class=\"header-info\">\n <mat-icon class=\"header-icon\">smart_toy</mat-icon>\n <div class=\"header-text\">\n <span class=\"header-title\">{{ agentName }}</span>\n <span class=\"status-badge\" [class.online]=\"isOnline\" [class.offline]=\"!isOnline\">\n <span class=\"status-dot\"></span>\n {{ isOnline ? 'Online' : 'Offline' }}\n </span>\n </div>\n </div>\n <div class=\"header-actions\">\n <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\n <mat-icon>add_comment</mat-icon>\n </button>\n <button mat-icon-button matTooltip=\"Close\" (click)=\"toggleChat()\">\n <mat-icon>remove</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Messages area -->\n <div class=\"agent-messages\" #messageContainer>\n\n <!-- Welcome experience (show when no messages) -->\n <div class=\"agent-greeting\" *ngIf=\"messages.length === 0\">\n <mat-icon class=\"greeting-icon\">smart_toy</mat-icon>\n <p class=\"greeting-text\">{{ greeting }}</p>\n\n <!-- Three capability pillars -->\n <div class=\"capability-pillars\">\n\n <!-- Pillar 1: Navigate & Inquire -->\n <div class=\"pillar\" (click)=\"sendSuggested('How do I post a transaction?')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">explore</mat-icon>\n <span class=\"pillar-title\">Ask & Navigate</span>\n </div>\n <p class=\"pillar-desc\">Ask how to do things or find features in the app</p>\n <span class=\"pillar-example\">\"How do I post a transaction?\"</span>\n </div>\n\n <!-- Pillar 2: Create & Modify -->\n <div class=\"pillar\" (click)=\"sendSuggested('Create a new customer')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">edit_note</mat-icon>\n <span class=\"pillar-title\">Create & Modify</span>\n </div>\n <p class=\"pillar-desc\">Add, edit, or delete records using natural language</p>\n <span class=\"pillar-example\">\"Create a new customer called Acme Ltd\"</span>\n </div>\n\n <!-- Pillar 3: Get Information -->\n <div class=\"pillar\" (click)=\"sendSuggested('List all accounts')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">search</mat-icon>\n <span class=\"pillar-title\">Get Information</span>\n </div>\n <p class=\"pillar-desc\">Retrieve details, list records, or get summaries</p>\n <span class=\"pillar-example\">\"Show me the details of Customer A\"</span>\n </div>\n\n </div>\n </div>\n\n <!-- Message bubbles -->\n <div *ngFor=\"let msg of messages; trackBy: trackMessage\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\">\n {{ msg.content }}\n </div>\n </div>\n\n <!-- Typing indicator -->\n <div class=\"message-row agent-row\" *ngIf=\"isTyping\">\n <div class=\"message-bubble agent-bubble typing-bubble\">\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n </div>\n </div>\n\n </div>\n\n <!-- Input area -->\n <div class=\"agent-input\">\n <mat-form-field appearance=\"outline\" class=\"input-field\">\n <input matInput #messageInput placeholder=\"Type a message...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\n </mat-form-field>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!inputText.trim()\">\n <mat-icon>send</mat-icon>\n </button>\n </div>\n\n</div>\n", styles: [".agent-fab{position:fixed;bottom:24px;right:24px;z-index:1001;background:#1976d2;color:#fff}.agent-window{position:fixed;bottom:96px;right:24px;width:400px;height:580px;background:#fff;border-radius:16px;box-shadow:0 8px 32px #00000026;display:flex;flex-direction:column;z-index:1000;overflow:hidden}.agent-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#1976d2;color:#fff;min-height:56px}.header-info{display:flex;align-items:center;gap:10px}.header-icon{font-size:24px;width:24px;height:24px}.header-text{display:flex;flex-direction:column;gap:1px}.header-title{font-size:16px;font-weight:500;line-height:1.2}.status-badge{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:400;opacity:.9;line-height:1}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block}.status-badge.online .status-dot{background:#4caf50;box-shadow:0 0 4px #4caf5099}.status-badge.offline .status-dot{background:#9e9e9e}.header-actions{display:flex;gap:0}.header-actions button{color:#fff}.agent-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}.agent-greeting{display:flex;flex-direction:column;align-items:center;text-align:center;padding:20px 8px 8px;gap:10px}.greeting-icon{font-size:48px;width:48px;height:48px;color:#1976d2;opacity:.8}.greeting-text{font-size:15px;color:#424242;line-height:1.5;margin:0}.capability-pillars{display:flex;flex-direction:column;gap:10px;margin-top:8px;width:100%}.pillar{border:1px solid #e0e0e0;border-radius:12px;padding:12px 14px;text-align:left;cursor:pointer;transition:all .2s ease;background:#fafafa}.pillar:hover{border-color:#1976d2;background:#1976d20a;box-shadow:0 2px 8px #1976d21a}.pillar-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}.pillar-icon{font-size:20px;width:20px;height:20px;color:#1976d2}.pillar-title{font-size:13px;font-weight:600;color:#212121}.pillar-desc{font-size:12px;color:#616161;margin:0 0 6px;line-height:1.4}.pillar-example{font-size:12px;color:#1976d2;font-style:italic;opacity:.85}.message-row{display:flex;max-width:85%}.user-row{align-self:flex-end}.agent-row{align-self:flex-start}.message-bubble{padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.5;word-break:break-word;white-space:pre-wrap}.user-bubble{background:#1976d2;color:#fff;border-bottom-right-radius:4px}.agent-bubble{background:#f0f0f0;color:#212121;border-bottom-left-radius:4px}.typing-bubble{display:flex;align-items:center;gap:4px;padding:12px 18px}.typing-dot{width:8px;height:8px;border-radius:50%;background:#9e9e9e;animation:typingBounce 1.4s infinite ease-in-out}.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}@keyframes typingBounce{0%,60%,to{transform:translateY(0);opacity:.4}30%{transform:translateY(-6px);opacity:1}}.agent-input{display:flex;align-items:center;padding:8px 12px;border-top:1px solid #e0e0e0;gap:4px}.input-field{flex:1}.input-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.input-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.send-btn{margin-bottom:4px}@media (max-width: 600px){.agent-window{inset:0 0 auto;width:100%;height:100%;height:100dvh;height:var(--agent-vh, 100dvh);border-radius:0}.agent-fab{bottom:72px;right:16px}}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], animations: [
|
|
10166
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: AgentComponent, isStandalone: false, selector: "spa-agent", host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "messageContainer", first: true, predicate: ["messageContainer"], descendants: true }, { propertyName: "messageInput", first: true, predicate: ["messageInput"], descendants: true }], ngImport: i0, template: "<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\r\n\r\n<!-- FAB toggle button -->\r\n<button mat-fab class=\"agent-fab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\r\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\r\n</button>\r\n\r\n<!-- Chat window -->\r\n<div class=\"agent-window\" *ngIf=\"isOpen\" @slideUp>\r\n\r\n <!-- Header with online status -->\r\n <div class=\"agent-header\">\r\n <div class=\"header-info\">\r\n <mat-icon class=\"header-icon\">smart_toy</mat-icon>\r\n <div class=\"header-text\">\r\n <span class=\"header-title\">{{ agentName }}</span>\r\n <span class=\"status-badge\" [class.online]=\"isOnline\" [class.offline]=\"!isOnline\">\r\n <span class=\"status-dot\"></span>\r\n {{ isOnline ? 'Online' : 'Offline' }}\r\n </span>\r\n </div>\r\n </div>\r\n <div class=\"header-actions\">\r\n <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\r\n <mat-icon>add_comment</mat-icon>\r\n </button>\r\n <button mat-icon-button matTooltip=\"Close\" (click)=\"toggleChat()\">\r\n <mat-icon>remove</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Messages area -->\r\n <div class=\"agent-messages\" #messageContainer>\r\n\r\n <!-- Welcome experience (show when no messages) -->\r\n <div class=\"agent-greeting\" *ngIf=\"messages.length === 0\">\r\n <mat-icon class=\"greeting-icon\">smart_toy</mat-icon>\r\n <p class=\"greeting-text\">{{ greeting }}</p>\r\n\r\n <!-- Three capability pillars -->\r\n <div class=\"capability-pillars\">\r\n\r\n <!-- Pillar 1: Navigate & Inquire -->\r\n <div class=\"pillar\" (click)=\"sendSuggested('How do I post a transaction?')\">\r\n <div class=\"pillar-header\">\r\n <mat-icon class=\"pillar-icon\">explore</mat-icon>\r\n <span class=\"pillar-title\">Ask & Navigate</span>\r\n </div>\r\n <p class=\"pillar-desc\">Ask how to do things or find features in the app</p>\r\n <span class=\"pillar-example\">\"How do I post a transaction?\"</span>\r\n </div>\r\n\r\n <!-- Pillar 2: Create & Modify -->\r\n <div class=\"pillar\" (click)=\"sendSuggested('Create a new customer')\">\r\n <div class=\"pillar-header\">\r\n <mat-icon class=\"pillar-icon\">edit_note</mat-icon>\r\n <span class=\"pillar-title\">Create & Modify</span>\r\n </div>\r\n <p class=\"pillar-desc\">Add, edit, or delete records using natural language</p>\r\n <span class=\"pillar-example\">\"Create a new customer called Acme Ltd\"</span>\r\n </div>\r\n\r\n <!-- Pillar 3: Get Information -->\r\n <div class=\"pillar\" (click)=\"sendSuggested('List all accounts')\">\r\n <div class=\"pillar-header\">\r\n <mat-icon class=\"pillar-icon\">search</mat-icon>\r\n <span class=\"pillar-title\">Get Information</span>\r\n </div>\r\n <p class=\"pillar-desc\">Retrieve details, list records, or get summaries</p>\r\n <span class=\"pillar-example\">\"Show me the details of Customer A\"</span>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Message bubbles -->\r\n <div *ngFor=\"let msg of messages; trackBy: trackMessage\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\r\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\">\r\n {{ msg.content }}\r\n </div>\r\n </div>\r\n\r\n <!-- Typing indicator -->\r\n <div class=\"message-row agent-row\" *ngIf=\"isTyping\">\r\n <div class=\"message-bubble agent-bubble typing-bubble\">\r\n <span class=\"typing-dot\"></span>\r\n <span class=\"typing-dot\"></span>\r\n <span class=\"typing-dot\"></span>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n\r\n <!-- Input area -->\r\n <div class=\"agent-input\">\r\n <mat-form-field appearance=\"outline\" class=\"input-field\">\r\n <input matInput #messageInput placeholder=\"Type a message...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\r\n </mat-form-field>\r\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!inputText.trim()\">\r\n <mat-icon>send</mat-icon>\r\n </button>\r\n </div>\r\n\r\n</div>\r\n", styles: [".agent-fab{position:fixed;bottom:24px;right:24px;z-index:1001;background:#1976d2;color:#fff}.agent-window{position:fixed;bottom:96px;right:24px;width:400px;height:580px;background:#fff;border-radius:16px;box-shadow:0 8px 32px #00000026;display:flex;flex-direction:column;z-index:1000;overflow:hidden}.agent-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#1976d2;color:#fff;min-height:56px}.header-info{display:flex;align-items:center;gap:10px}.header-icon{font-size:24px;width:24px;height:24px}.header-text{display:flex;flex-direction:column;gap:1px}.header-title{font-size:16px;font-weight:500;line-height:1.2}.status-badge{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:400;opacity:.9;line-height:1}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block}.status-badge.online .status-dot{background:#4caf50;box-shadow:0 0 4px #4caf5099}.status-badge.offline .status-dot{background:#9e9e9e}.header-actions{display:flex;gap:0}.header-actions button{color:#fff}.agent-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}.agent-greeting{display:flex;flex-direction:column;align-items:center;text-align:center;padding:20px 8px 8px;gap:10px}.greeting-icon{font-size:48px;width:48px;height:48px;color:#1976d2;opacity:.8}.greeting-text{font-size:15px;color:#424242;line-height:1.5;margin:0}.capability-pillars{display:flex;flex-direction:column;gap:10px;margin-top:8px;width:100%}.pillar{border:1px solid #e0e0e0;border-radius:12px;padding:12px 14px;text-align:left;cursor:pointer;transition:all .2s ease;background:#fafafa}.pillar:hover{border-color:#1976d2;background:#1976d20a;box-shadow:0 2px 8px #1976d21a}.pillar-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}.pillar-icon{font-size:20px;width:20px;height:20px;color:#1976d2}.pillar-title{font-size:13px;font-weight:600;color:#212121}.pillar-desc{font-size:12px;color:#616161;margin:0 0 6px;line-height:1.4}.pillar-example{font-size:12px;color:#1976d2;font-style:italic;opacity:.85}.message-row{display:flex;max-width:85%}.user-row{align-self:flex-end}.agent-row{align-self:flex-start}.message-bubble{padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.5;word-break:break-word;white-space:pre-wrap}.user-bubble{background:#1976d2;color:#fff;border-bottom-right-radius:4px}.agent-bubble{background:#f0f0f0;color:#212121;border-bottom-left-radius:4px}.typing-bubble{display:flex;align-items:center;gap:4px;padding:12px 18px}.typing-dot{width:8px;height:8px;border-radius:50%;background:#9e9e9e;animation:typingBounce 1.4s infinite ease-in-out}.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}@keyframes typingBounce{0%,60%,to{transform:translateY(0);opacity:.4}30%{transform:translateY(-6px);opacity:1}}.agent-input{display:flex;align-items:center;padding:8px 12px;border-top:1px solid #e0e0e0;gap:4px}.input-field{flex:1}.input-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.input-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.send-btn{margin-bottom:4px}@media (max-width: 600px){.agent-window{inset:0 0 auto;width:100%;height:100%;height:100dvh;height:var(--agent-vh, 100dvh);border-radius:0}.agent-fab{bottom:72px;right:16px}}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], animations: [
|
|
10063
10167
|
trigger('slideUp', [
|
|
10064
10168
|
transition(':enter', [
|
|
10065
10169
|
style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }),
|
|
@@ -10083,7 +10187,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
10083
10187
|
animate('200ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }))
|
|
10084
10188
|
])
|
|
10085
10189
|
])
|
|
10086
|
-
], template: "<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\n\n<!-- FAB toggle button -->\n<button mat-fab class=\"agent-fab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\n</button>\n\n<!-- Chat window -->\n<div class=\"agent-window\" *ngIf=\"isOpen\" @slideUp>\n\n <!-- Header with online status -->\n <div class=\"agent-header\">\n <div class=\"header-info\">\n <mat-icon class=\"header-icon\">smart_toy</mat-icon>\n <div class=\"header-text\">\n <span class=\"header-title\">{{ agentName }}</span>\n <span class=\"status-badge\" [class.online]=\"isOnline\" [class.offline]=\"!isOnline\">\n <span class=\"status-dot\"></span>\n {{ isOnline ? 'Online' : 'Offline' }}\n </span>\n </div>\n </div>\n <div class=\"header-actions\">\n <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\n <mat-icon>add_comment</mat-icon>\n </button>\n <button mat-icon-button matTooltip=\"Close\" (click)=\"toggleChat()\">\n <mat-icon>remove</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Messages area -->\n <div class=\"agent-messages\" #messageContainer>\n\n <!-- Welcome experience (show when no messages) -->\n <div class=\"agent-greeting\" *ngIf=\"messages.length === 0\">\n <mat-icon class=\"greeting-icon\">smart_toy</mat-icon>\n <p class=\"greeting-text\">{{ greeting }}</p>\n\n <!-- Three capability pillars -->\n <div class=\"capability-pillars\">\n\n <!-- Pillar 1: Navigate & Inquire -->\n <div class=\"pillar\" (click)=\"sendSuggested('How do I post a transaction?')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">explore</mat-icon>\n <span class=\"pillar-title\">Ask & Navigate</span>\n </div>\n <p class=\"pillar-desc\">Ask how to do things or find features in the app</p>\n <span class=\"pillar-example\">\"How do I post a transaction?\"</span>\n </div>\n\n <!-- Pillar 2: Create & Modify -->\n <div class=\"pillar\" (click)=\"sendSuggested('Create a new customer')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">edit_note</mat-icon>\n <span class=\"pillar-title\">Create & Modify</span>\n </div>\n <p class=\"pillar-desc\">Add, edit, or delete records using natural language</p>\n <span class=\"pillar-example\">\"Create a new customer called Acme Ltd\"</span>\n </div>\n\n <!-- Pillar 3: Get Information -->\n <div class=\"pillar\" (click)=\"sendSuggested('List all accounts')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">search</mat-icon>\n <span class=\"pillar-title\">Get Information</span>\n </div>\n <p class=\"pillar-desc\">Retrieve details, list records, or get summaries</p>\n <span class=\"pillar-example\">\"Show me the details of Customer A\"</span>\n </div>\n\n </div>\n </div>\n\n <!-- Message bubbles -->\n <div *ngFor=\"let msg of messages; trackBy: trackMessage\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\">\n {{ msg.content }}\n </div>\n </div>\n\n <!-- Typing indicator -->\n <div class=\"message-row agent-row\" *ngIf=\"isTyping\">\n <div class=\"message-bubble agent-bubble typing-bubble\">\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n </div>\n </div>\n\n </div>\n\n <!-- Input area -->\n <div class=\"agent-input\">\n <mat-form-field appearance=\"outline\" class=\"input-field\">\n <input matInput #messageInput placeholder=\"Type a message...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\n </mat-form-field>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!inputText.trim()\">\n <mat-icon>send</mat-icon>\n </button>\n </div>\n\n</div>\n", styles: [".agent-fab{position:fixed;bottom:24px;right:24px;z-index:1001;background:#1976d2;color:#fff}.agent-window{position:fixed;bottom:96px;right:24px;width:400px;height:580px;background:#fff;border-radius:16px;box-shadow:0 8px 32px #00000026;display:flex;flex-direction:column;z-index:1000;overflow:hidden}.agent-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#1976d2;color:#fff;min-height:56px}.header-info{display:flex;align-items:center;gap:10px}.header-icon{font-size:24px;width:24px;height:24px}.header-text{display:flex;flex-direction:column;gap:1px}.header-title{font-size:16px;font-weight:500;line-height:1.2}.status-badge{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:400;opacity:.9;line-height:1}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block}.status-badge.online .status-dot{background:#4caf50;box-shadow:0 0 4px #4caf5099}.status-badge.offline .status-dot{background:#9e9e9e}.header-actions{display:flex;gap:0}.header-actions button{color:#fff}.agent-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}.agent-greeting{display:flex;flex-direction:column;align-items:center;text-align:center;padding:20px 8px 8px;gap:10px}.greeting-icon{font-size:48px;width:48px;height:48px;color:#1976d2;opacity:.8}.greeting-text{font-size:15px;color:#424242;line-height:1.5;margin:0}.capability-pillars{display:flex;flex-direction:column;gap:10px;margin-top:8px;width:100%}.pillar{border:1px solid #e0e0e0;border-radius:12px;padding:12px 14px;text-align:left;cursor:pointer;transition:all .2s ease;background:#fafafa}.pillar:hover{border-color:#1976d2;background:#1976d20a;box-shadow:0 2px 8px #1976d21a}.pillar-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}.pillar-icon{font-size:20px;width:20px;height:20px;color:#1976d2}.pillar-title{font-size:13px;font-weight:600;color:#212121}.pillar-desc{font-size:12px;color:#616161;margin:0 0 6px;line-height:1.4}.pillar-example{font-size:12px;color:#1976d2;font-style:italic;opacity:.85}.message-row{display:flex;max-width:85%}.user-row{align-self:flex-end}.agent-row{align-self:flex-start}.message-bubble{padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.5;word-break:break-word;white-space:pre-wrap}.user-bubble{background:#1976d2;color:#fff;border-bottom-right-radius:4px}.agent-bubble{background:#f0f0f0;color:#212121;border-bottom-left-radius:4px}.typing-bubble{display:flex;align-items:center;gap:4px;padding:12px 18px}.typing-dot{width:8px;height:8px;border-radius:50%;background:#9e9e9e;animation:typingBounce 1.4s infinite ease-in-out}.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}@keyframes typingBounce{0%,60%,to{transform:translateY(0);opacity:.4}30%{transform:translateY(-6px);opacity:1}}.agent-input{display:flex;align-items:center;padding:8px 12px;border-top:1px solid #e0e0e0;gap:4px}.input-field{flex:1}.input-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.input-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.send-btn{margin-bottom:4px}@media (max-width: 600px){.agent-window{inset:0 0 auto;width:100%;height:100%;height:100dvh;height:var(--agent-vh, 100dvh);border-radius:0}.agent-fab{bottom:72px;right:16px}}\n"] }]
|
|
10190
|
+
], template: "<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\r\n\r\n<!-- FAB toggle button -->\r\n<button mat-fab class=\"agent-fab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\r\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\r\n</button>\r\n\r\n<!-- Chat window -->\r\n<div class=\"agent-window\" *ngIf=\"isOpen\" @slideUp>\r\n\r\n <!-- Header with online status -->\r\n <div class=\"agent-header\">\r\n <div class=\"header-info\">\r\n <mat-icon class=\"header-icon\">smart_toy</mat-icon>\r\n <div class=\"header-text\">\r\n <span class=\"header-title\">{{ agentName }}</span>\r\n <span class=\"status-badge\" [class.online]=\"isOnline\" [class.offline]=\"!isOnline\">\r\n <span class=\"status-dot\"></span>\r\n {{ isOnline ? 'Online' : 'Offline' }}\r\n </span>\r\n </div>\r\n </div>\r\n <div class=\"header-actions\">\r\n <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\r\n <mat-icon>add_comment</mat-icon>\r\n </button>\r\n <button mat-icon-button matTooltip=\"Close\" (click)=\"toggleChat()\">\r\n <mat-icon>remove</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Messages area -->\r\n <div class=\"agent-messages\" #messageContainer>\r\n\r\n <!-- Welcome experience (show when no messages) -->\r\n <div class=\"agent-greeting\" *ngIf=\"messages.length === 0\">\r\n <mat-icon class=\"greeting-icon\">smart_toy</mat-icon>\r\n <p class=\"greeting-text\">{{ greeting }}</p>\r\n\r\n <!-- Three capability pillars -->\r\n <div class=\"capability-pillars\">\r\n\r\n <!-- Pillar 1: Navigate & Inquire -->\r\n <div class=\"pillar\" (click)=\"sendSuggested('How do I post a transaction?')\">\r\n <div class=\"pillar-header\">\r\n <mat-icon class=\"pillar-icon\">explore</mat-icon>\r\n <span class=\"pillar-title\">Ask & Navigate</span>\r\n </div>\r\n <p class=\"pillar-desc\">Ask how to do things or find features in the app</p>\r\n <span class=\"pillar-example\">\"How do I post a transaction?\"</span>\r\n </div>\r\n\r\n <!-- Pillar 2: Create & Modify -->\r\n <div class=\"pillar\" (click)=\"sendSuggested('Create a new customer')\">\r\n <div class=\"pillar-header\">\r\n <mat-icon class=\"pillar-icon\">edit_note</mat-icon>\r\n <span class=\"pillar-title\">Create & Modify</span>\r\n </div>\r\n <p class=\"pillar-desc\">Add, edit, or delete records using natural language</p>\r\n <span class=\"pillar-example\">\"Create a new customer called Acme Ltd\"</span>\r\n </div>\r\n\r\n <!-- Pillar 3: Get Information -->\r\n <div class=\"pillar\" (click)=\"sendSuggested('List all accounts')\">\r\n <div class=\"pillar-header\">\r\n <mat-icon class=\"pillar-icon\">search</mat-icon>\r\n <span class=\"pillar-title\">Get Information</span>\r\n </div>\r\n <p class=\"pillar-desc\">Retrieve details, list records, or get summaries</p>\r\n <span class=\"pillar-example\">\"Show me the details of Customer A\"</span>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Message bubbles -->\r\n <div *ngFor=\"let msg of messages; trackBy: trackMessage\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\r\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\">\r\n {{ msg.content }}\r\n </div>\r\n </div>\r\n\r\n <!-- Typing indicator -->\r\n <div class=\"message-row agent-row\" *ngIf=\"isTyping\">\r\n <div class=\"message-bubble agent-bubble typing-bubble\">\r\n <span class=\"typing-dot\"></span>\r\n <span class=\"typing-dot\"></span>\r\n <span class=\"typing-dot\"></span>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n\r\n <!-- Input area -->\r\n <div class=\"agent-input\">\r\n <mat-form-field appearance=\"outline\" class=\"input-field\">\r\n <input matInput #messageInput placeholder=\"Type a message...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\r\n </mat-form-field>\r\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!inputText.trim()\">\r\n <mat-icon>send</mat-icon>\r\n </button>\r\n </div>\r\n\r\n</div>\r\n", styles: [".agent-fab{position:fixed;bottom:24px;right:24px;z-index:1001;background:#1976d2;color:#fff}.agent-window{position:fixed;bottom:96px;right:24px;width:400px;height:580px;background:#fff;border-radius:16px;box-shadow:0 8px 32px #00000026;display:flex;flex-direction:column;z-index:1000;overflow:hidden}.agent-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#1976d2;color:#fff;min-height:56px}.header-info{display:flex;align-items:center;gap:10px}.header-icon{font-size:24px;width:24px;height:24px}.header-text{display:flex;flex-direction:column;gap:1px}.header-title{font-size:16px;font-weight:500;line-height:1.2}.status-badge{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:400;opacity:.9;line-height:1}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block}.status-badge.online .status-dot{background:#4caf50;box-shadow:0 0 4px #4caf5099}.status-badge.offline .status-dot{background:#9e9e9e}.header-actions{display:flex;gap:0}.header-actions button{color:#fff}.agent-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}.agent-greeting{display:flex;flex-direction:column;align-items:center;text-align:center;padding:20px 8px 8px;gap:10px}.greeting-icon{font-size:48px;width:48px;height:48px;color:#1976d2;opacity:.8}.greeting-text{font-size:15px;color:#424242;line-height:1.5;margin:0}.capability-pillars{display:flex;flex-direction:column;gap:10px;margin-top:8px;width:100%}.pillar{border:1px solid #e0e0e0;border-radius:12px;padding:12px 14px;text-align:left;cursor:pointer;transition:all .2s ease;background:#fafafa}.pillar:hover{border-color:#1976d2;background:#1976d20a;box-shadow:0 2px 8px #1976d21a}.pillar-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}.pillar-icon{font-size:20px;width:20px;height:20px;color:#1976d2}.pillar-title{font-size:13px;font-weight:600;color:#212121}.pillar-desc{font-size:12px;color:#616161;margin:0 0 6px;line-height:1.4}.pillar-example{font-size:12px;color:#1976d2;font-style:italic;opacity:.85}.message-row{display:flex;max-width:85%}.user-row{align-self:flex-end}.agent-row{align-self:flex-start}.message-bubble{padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.5;word-break:break-word;white-space:pre-wrap}.user-bubble{background:#1976d2;color:#fff;border-bottom-right-radius:4px}.agent-bubble{background:#f0f0f0;color:#212121;border-bottom-left-radius:4px}.typing-bubble{display:flex;align-items:center;gap:4px;padding:12px 18px}.typing-dot{width:8px;height:8px;border-radius:50%;background:#9e9e9e;animation:typingBounce 1.4s infinite ease-in-out}.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}@keyframes typingBounce{0%,60%,to{transform:translateY(0);opacity:.4}30%{transform:translateY(-6px);opacity:1}}.agent-input{display:flex;align-items:center;padding:8px 12px;border-top:1px solid #e0e0e0;gap:4px}.input-field{flex:1}.input-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.input-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.send-btn{margin-bottom:4px}@media (max-width: 600px){.agent-window{inset:0 0 auto;width:100%;height:100%;height:100dvh;height:var(--agent-vh, 100dvh);border-radius:0}.agent-fab{bottom:72px;right:16px}}\n"] }]
|
|
10087
10191
|
}], ctorParameters: () => [{ type: AgentService }, { type: SignalRService }], propDecorators: { messageContainer: [{
|
|
10088
10192
|
type: ViewChild,
|
|
10089
10193
|
args: ['messageContainer']
|
|
@@ -10163,7 +10267,9 @@ class NavMenuComponent {
|
|
|
10163
10267
|
}
|
|
10164
10268
|
}
|
|
10165
10269
|
getSubItems(capItem) {
|
|
10166
|
-
|
|
10270
|
+
// Changed: still hide pure boolean permission caps, but allow navigable isBool
|
|
10271
|
+
// items (those with a link) to appear as sub-menu items
|
|
10272
|
+
return capItem.capSubItems.filter(x => x.showMenu && (!x.isBool || (x.link && x.link.trim() !== '')));
|
|
10167
10273
|
}
|
|
10168
10274
|
// Added: Check if a capItem is enabled by subscription plan (featureKey gating)
|
|
10169
10275
|
isFeatureAllowed(cap) {
|
|
@@ -10255,11 +10361,11 @@ class NavMenuComponent {
|
|
|
10255
10361
|
return !this.isMiniSidebar || this.isMiniHovered;
|
|
10256
10362
|
}
|
|
10257
10363
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: NavMenuComponent, deps: [{ token: i1$2.Router }, { token: AuthService }, { token: StorageService }, { token: NotificationsService }, { token: i1$3.BreakpointObserver }, { token: DataServiceLib }, { token: i1.MatDialog }, { token: SubscriptionService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10258
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: NavMenuComponent, isStandalone: false, selector: "spa-nav-menu", inputs: { appConfig: "appConfig", footer: "footer" }, host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> {{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<!-- SIDE -->\r\n<mat-toolbar class=\"tin-bg-image-toolbar\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\" style=\"padding: 0px 8px;\">\r\n\r\n <button mat-icon-button (click)=\"toggle()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <img [src]=\"dataService.appConfig.logo\" style=\"height: 50px;\" />\r\n\r\n <div style=\"padding-left: 10px; \">\r\n\r\n <div style=\"font-size: 22px; font-weight: 400;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <!-- <div style=\"font-size: 20px; height: 25px; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div> -->\r\n\r\n <!-- <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <span class=\"toolbar-item-spacer\"></span>\r\n\r\n <!-- buttons -->\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n\r\n <!-- <label style=\"font-size: 14px;\">Hi, {{loggedUserFullName}}</label> -->\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <label style=\"font-size: 14px;margin-right: 20px;\">{{tenantName}}</label>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">© {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n © {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4$3.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$3.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$3.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: i11.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "component", type: i5.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i14.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i14.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i14.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i16.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i16.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i16.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "component", type: i17.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "directive", type: i1$2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: i18.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i18.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "component", type: LoaderComponent, selector: "spa-loader", inputs: ["logo"] }, { kind: "component", type: ToastComponent, selector: "spa-toast" }, { kind: "component", type: AgentComponent, selector: "spa-agent" }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.DatePipe, name: "date" }] }); }
|
|
10364
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: NavMenuComponent, isStandalone: false, selector: "spa-nav-menu", inputs: { appConfig: "appConfig", footer: "footer" }, host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> {{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- ============================================================ -->\r\n<!-- TOP-MODERN (navigation == 'top-modern') -->\r\n<!-- Changed: Modernised horizontal top navigation -->\r\n<!-- ============================================================ -->\r\n<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n\r\n <nav class=\"tm-navbar\" [class.tm-scrolled]=\"topbarScrolled\">\r\n <div class=\"tm-bar\">\r\n\r\n <!-- Brand -->\r\n <div class=\"tm-brand\" (click)=\"modernNavigate('')\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" class=\"tm-logo\" alt=\"logo\" />\r\n <div class=\"tm-brand-text\">\r\n <div class=\"tm-app-name\">{{appConfig.appName}}</div>\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" class=\"tm-tenant\">{{tenantName}}</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Mobile toggle -->\r\n <button class=\"tm-toggler\" type=\"button\" aria-label=\"Toggle navigation\"\r\n [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <mat-icon>{{isExpanded ? 'close' : 'menu'}}</mat-icon>\r\n </button>\r\n\r\n <!-- Menu items -->\r\n <div class=\"tm-menu\" [class.tm-menu-open]=\"isExpanded\">\r\n\r\n <ng-container *ngFor=\"let item of dataService.appConfig.capItems\">\r\n <div class=\"tm-item-wrap\"\r\n *ngIf=\"myRole[item.name] && item.showMenu && isFeatureAllowed(item)\">\r\n\r\n <!-- Simple item (no sub-items, or sub-items ignored for display) -->\r\n <button *ngIf=\"!item.capSubItems || item.ignoreSubsDisplay\"\r\n class=\"tm-item\"\r\n [class.tm-item-active]=\"isActiveRoute(item.link)\"\r\n (click)=\"modernNavigate(item.link)\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n </button>\r\n\r\n <!-- Parent item with displayed sub-items -->\r\n <ng-container *ngIf=\"item.capSubItems && !item.ignoreSubsDisplay\">\r\n <button class=\"tm-item tm-item-parent\"\r\n [class.tm-item-active]=\"isParentActive(item)\"\r\n [matMenuTriggerFor]=\"tmSubMenu\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n <!-- Changed: Caret signals this item has more items beneath it -->\r\n <mat-icon class=\"tm-item-caret\">expand_more</mat-icon>\r\n </button>\r\n\r\n <mat-menu #tmSubMenu=\"matMenu\" class=\"tm-submenu-panel\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <ng-container *ngFor=\"let sub of getSubItems(item)\">\r\n <button *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n mat-menu-item\r\n [class.tm-sub-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\">{{sub.icon}}</mat-icon>\r\n <span>{{sub.display}}</span>\r\n </button>\r\n </ng-container>\r\n </mat-menu>\r\n </ng-container>\r\n\r\n </div>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n <!-- Right-side actions -->\r\n <div class=\"tm-actions\" [class.tm-actions-open]=\"isExpanded\">\r\n\r\n <ng-container *ngIf=\"dataService.appConfig.multitenant\">\r\n <button mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n </ng-container>\r\n\r\n <span class=\"tm-divider-v\"></span>\r\n\r\n <!-- Profile -->\r\n <button mat-button class=\"tm-user-btn\" [matMenuTriggerFor]=\"tmProfileMenu\">\r\n <mat-icon class=\"tm-user-icon\">account_circle</mat-icon>\r\n <span class=\"tm-user-name\">{{loggedUserFullName}}</span>\r\n </button>\r\n\r\n <mat-menu #tmProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item (click)=\"modernNavigate('home/user/profile')\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon><span>Log Off</span>\r\n </button>\r\n </mat-menu>\r\n\r\n <!-- Sign out \u2014 Changed: aligned via flex-centred action button -->\r\n <button mat-icon-button class=\"tm-action-btn tm-signout\" (click)=\"logoff()\" matTooltip=\"Sign Out\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n </div>\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Top-modern page content \u2014 Changed: use original 'top' background (tin-bg-image), no footer bar -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Not logged in fallback for top-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- SIDE -->\r\n<mat-toolbar class=\"tin-bg-image-toolbar\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\" style=\"padding: 0px 8px;\">\r\n\r\n <button mat-icon-button (click)=\"toggle()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <img [src]=\"dataService.appConfig.logo\" style=\"height: 50px;\" />\r\n\r\n <div style=\"padding-left: 10px; \">\r\n\r\n <div style=\"font-size: 22px; font-weight: 400;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <!-- <div style=\"font-size: 20px; height: 25px; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div> -->\r\n\r\n <!-- <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <span class=\"toolbar-item-spacer\"></span>\r\n\r\n <!-- buttons -->\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n\r\n <!-- <label style=\"font-size: 14px;\">Hi, {{loggedUserFullName}}</label> -->\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <label style=\"font-size: 14px;margin-right: 20px;\">{{tenantName}}</label>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">© {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n © {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}.tm-navbar{position:sticky;top:0;z-index:1030;background-color:#03a;color:#fff;box-shadow:0 2px 12px #0000001f;transition:background-color .3s ease,backdrop-filter .3s ease,box-shadow .3s ease}.tm-navbar.tm-scrolled{background-color:#0033aad9;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 4px 18px #0000002e}.tm-bar{display:flex;align-items:center;flex-wrap:nowrap;min-height:60px;padding:6px 16px;gap:4px}.tm-brand{display:flex;align-items:center;gap:12px;flex-shrink:0;margin-right:18px;cursor:pointer;-webkit-user-select:none;user-select:none}.tm-logo{height:40px;width:auto;object-fit:contain}.tm-app-name{font-size:20px;font-weight:500;line-height:1.2;white-space:nowrap}.tm-tenant{font-size:12px;font-weight:400;color:#ffffffb3;line-height:1.2}.tm-toggler{display:none;margin-left:auto;background:transparent;border:1px solid rgba(255,255,255,.4);border-radius:8px;color:#fff;cursor:pointer;align-items:center;justify-content:center;width:40px;height:40px}.tm-toggler mat-icon{color:#fff}.tm-menu{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 auto;min-width:0;row-gap:4px}.tm-item-wrap{display:flex;align-items:center;position:relative}.tm-item-wrap:not(:first-child):before{content:\"\";width:1px;height:18px;background:#ffffff2e;margin:0 2px;flex-shrink:0}.tm-item{position:relative;display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 14px;margin:0 2px;background:transparent;border:none;border-radius:8px;color:#ffffffeb;font-size:14px;font-weight:400;letter-spacing:.2px;white-space:nowrap;cursor:pointer;transition:background .18s ease,color .18s ease}.tm-item:after{content:\"\";position:absolute;left:12px;right:12px;bottom:5px;height:3px;border-radius:3px;background:#fff;transform:scaleX(0);transform-origin:center;transition:transform .25s cubic-bezier(.4,0,.2,1)}.tm-item:hover{color:#fff}.tm-item:hover:after{transform:scaleX(1)}.tm-item.tm-item-active:after{transform:scaleX(1)}.tm-item-icon{font-size:19px!important;width:19px!important;height:19px!important;display:inline-flex!important;align-items:center;justify-content:center;color:inherit!important}.tm-item-caret{font-size:18px!important;width:18px!important;height:18px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-left:-2px;margin-right:-4px;color:#ffffffb3!important;transition:transform .2s ease}.tm-item:hover .tm-item-caret,.tm-item-active .tm-item-caret{color:#fff!important}.tm-actions{display:flex;align-items:center;gap:2px;flex-shrink:0;margin-left:auto}.tm-actions .mat-mdc-icon-button,.tm-action-btn{display:inline-flex!important;align-items:center!important;justify-content:center!important;color:#fff!important}.tm-actions mat-icon{color:#fff!important}.tm-divider-v{width:1px;height:24px;background:#ffffff38;margin:0 6px;flex-shrink:0}.tm-user-btn{display:inline-flex!important;align-items:center!important;gap:6px;height:40px;color:#fff!important;border-radius:8px;transition:background .18s ease}.tm-user-btn:hover{background:#ffffff1f}.tm-user-icon{font-size:24px!important;width:24px!important;height:24px!important;color:#fff!important}.tm-user-name{font-size:14px;font-weight:400;white-space:nowrap}::ng-deep .tm-submenu-panel .tm-sub-active{background:#0033aa14;font-weight:600;color:#03a}::ng-deep .tm-submenu-panel .tm-sub-active .mat-icon{color:#03a}@media (max-width: 991px){.tm-toggler{display:inline-flex}.tm-menu,.tm-actions{display:none;position:absolute;left:0;right:0;top:100%;flex-direction:column;align-items:stretch;background:#03a;padding:8px 12px;box-shadow:0 8px 18px #0003;z-index:1029}.tm-menu.tm-menu-open{display:flex}.tm-actions.tm-actions-open{display:flex;top:100%;border-top:1px solid rgba(255,255,255,.12)}.tm-item-wrap{width:100%}.tm-item-wrap:not(:first-child):before{width:100%;height:1px;margin:2px 0}.tm-item{width:100%;justify-content:flex-start;height:44px;margin:0}.tm-item:after{inset:8px auto 8px 0;width:4px;height:auto;transform:scaleY(0);transform-origin:center}.tm-item:hover:after,.tm-item.tm-item-active:after{transform:scaleY(1)}.tm-item-caret{margin-left:auto}.tm-divider-v{display:none}.tm-user-btn{justify-content:flex-start;width:100%}}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4$3.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$3.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$3.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: i11.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "component", type: i5.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i14.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i14.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i14.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i16.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i16.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i16.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "component", type: i17.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "directive", type: i1$2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: i18.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i18.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "component", type: LoaderComponent, selector: "spa-loader", inputs: ["logo"] }, { kind: "component", type: ToastComponent, selector: "spa-toast" }, { kind: "component", type: AgentComponent, selector: "spa-agent" }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.DatePipe, name: "date" }] }); }
|
|
10259
10365
|
}
|
|
10260
10366
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: NavMenuComponent, decorators: [{
|
|
10261
10367
|
type: Component,
|
|
10262
|
-
args: [{ selector: 'spa-nav-menu', standalone: false, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> {{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<!-- SIDE -->\r\n<mat-toolbar class=\"tin-bg-image-toolbar\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\" style=\"padding: 0px 8px;\">\r\n\r\n <button mat-icon-button (click)=\"toggle()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <img [src]=\"dataService.appConfig.logo\" style=\"height: 50px;\" />\r\n\r\n <div style=\"padding-left: 10px; \">\r\n\r\n <div style=\"font-size: 22px; font-weight: 400;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <!-- <div style=\"font-size: 20px; height: 25px; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div> -->\r\n\r\n <!-- <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <span class=\"toolbar-item-spacer\"></span>\r\n\r\n <!-- buttons -->\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n\r\n <!-- <label style=\"font-size: 14px;\">Hi, {{loggedUserFullName}}</label> -->\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <label style=\"font-size: 14px;margin-right: 20px;\">{{tenantName}}</label>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">© {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n © {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}\n"] }]
|
|
10368
|
+
args: [{ selector: 'spa-nav-menu', standalone: false, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> {{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- ============================================================ -->\r\n<!-- TOP-MODERN (navigation == 'top-modern') -->\r\n<!-- Changed: Modernised horizontal top navigation -->\r\n<!-- ============================================================ -->\r\n<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n\r\n <nav class=\"tm-navbar\" [class.tm-scrolled]=\"topbarScrolled\">\r\n <div class=\"tm-bar\">\r\n\r\n <!-- Brand -->\r\n <div class=\"tm-brand\" (click)=\"modernNavigate('')\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" class=\"tm-logo\" alt=\"logo\" />\r\n <div class=\"tm-brand-text\">\r\n <div class=\"tm-app-name\">{{appConfig.appName}}</div>\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" class=\"tm-tenant\">{{tenantName}}</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Mobile toggle -->\r\n <button class=\"tm-toggler\" type=\"button\" aria-label=\"Toggle navigation\"\r\n [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <mat-icon>{{isExpanded ? 'close' : 'menu'}}</mat-icon>\r\n </button>\r\n\r\n <!-- Menu items -->\r\n <div class=\"tm-menu\" [class.tm-menu-open]=\"isExpanded\">\r\n\r\n <ng-container *ngFor=\"let item of dataService.appConfig.capItems\">\r\n <div class=\"tm-item-wrap\"\r\n *ngIf=\"myRole[item.name] && item.showMenu && isFeatureAllowed(item)\">\r\n\r\n <!-- Simple item (no sub-items, or sub-items ignored for display) -->\r\n <button *ngIf=\"!item.capSubItems || item.ignoreSubsDisplay\"\r\n class=\"tm-item\"\r\n [class.tm-item-active]=\"isActiveRoute(item.link)\"\r\n (click)=\"modernNavigate(item.link)\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n </button>\r\n\r\n <!-- Parent item with displayed sub-items -->\r\n <ng-container *ngIf=\"item.capSubItems && !item.ignoreSubsDisplay\">\r\n <button class=\"tm-item tm-item-parent\"\r\n [class.tm-item-active]=\"isParentActive(item)\"\r\n [matMenuTriggerFor]=\"tmSubMenu\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n <!-- Changed: Caret signals this item has more items beneath it -->\r\n <mat-icon class=\"tm-item-caret\">expand_more</mat-icon>\r\n </button>\r\n\r\n <mat-menu #tmSubMenu=\"matMenu\" class=\"tm-submenu-panel\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <ng-container *ngFor=\"let sub of getSubItems(item)\">\r\n <button *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n mat-menu-item\r\n [class.tm-sub-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\">{{sub.icon}}</mat-icon>\r\n <span>{{sub.display}}</span>\r\n </button>\r\n </ng-container>\r\n </mat-menu>\r\n </ng-container>\r\n\r\n </div>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n <!-- Right-side actions -->\r\n <div class=\"tm-actions\" [class.tm-actions-open]=\"isExpanded\">\r\n\r\n <ng-container *ngIf=\"dataService.appConfig.multitenant\">\r\n <button mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n </ng-container>\r\n\r\n <span class=\"tm-divider-v\"></span>\r\n\r\n <!-- Profile -->\r\n <button mat-button class=\"tm-user-btn\" [matMenuTriggerFor]=\"tmProfileMenu\">\r\n <mat-icon class=\"tm-user-icon\">account_circle</mat-icon>\r\n <span class=\"tm-user-name\">{{loggedUserFullName}}</span>\r\n </button>\r\n\r\n <mat-menu #tmProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item (click)=\"modernNavigate('home/user/profile')\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon><span>Log Off</span>\r\n </button>\r\n </mat-menu>\r\n\r\n <!-- Sign out \u2014 Changed: aligned via flex-centred action button -->\r\n <button mat-icon-button class=\"tm-action-btn tm-signout\" (click)=\"logoff()\" matTooltip=\"Sign Out\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n </div>\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Top-modern page content \u2014 Changed: use original 'top' background (tin-bg-image), no footer bar -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Not logged in fallback for top-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- SIDE -->\r\n<mat-toolbar class=\"tin-bg-image-toolbar\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\" style=\"padding: 0px 8px;\">\r\n\r\n <button mat-icon-button (click)=\"toggle()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <img [src]=\"dataService.appConfig.logo\" style=\"height: 50px;\" />\r\n\r\n <div style=\"padding-left: 10px; \">\r\n\r\n <div style=\"font-size: 22px; font-weight: 400;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <!-- <div style=\"font-size: 20px; height: 25px; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div> -->\r\n\r\n <!-- <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <span class=\"toolbar-item-spacer\"></span>\r\n\r\n <!-- buttons -->\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n\r\n <!-- <label style=\"font-size: 14px;\">Hi, {{loggedUserFullName}}</label> -->\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <label style=\"font-size: 14px;margin-right: 20px;\">{{tenantName}}</label>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">© {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n © {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}.tm-navbar{position:sticky;top:0;z-index:1030;background-color:#03a;color:#fff;box-shadow:0 2px 12px #0000001f;transition:background-color .3s ease,backdrop-filter .3s ease,box-shadow .3s ease}.tm-navbar.tm-scrolled{background-color:#0033aad9;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 4px 18px #0000002e}.tm-bar{display:flex;align-items:center;flex-wrap:nowrap;min-height:60px;padding:6px 16px;gap:4px}.tm-brand{display:flex;align-items:center;gap:12px;flex-shrink:0;margin-right:18px;cursor:pointer;-webkit-user-select:none;user-select:none}.tm-logo{height:40px;width:auto;object-fit:contain}.tm-app-name{font-size:20px;font-weight:500;line-height:1.2;white-space:nowrap}.tm-tenant{font-size:12px;font-weight:400;color:#ffffffb3;line-height:1.2}.tm-toggler{display:none;margin-left:auto;background:transparent;border:1px solid rgba(255,255,255,.4);border-radius:8px;color:#fff;cursor:pointer;align-items:center;justify-content:center;width:40px;height:40px}.tm-toggler mat-icon{color:#fff}.tm-menu{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 auto;min-width:0;row-gap:4px}.tm-item-wrap{display:flex;align-items:center;position:relative}.tm-item-wrap:not(:first-child):before{content:\"\";width:1px;height:18px;background:#ffffff2e;margin:0 2px;flex-shrink:0}.tm-item{position:relative;display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 14px;margin:0 2px;background:transparent;border:none;border-radius:8px;color:#ffffffeb;font-size:14px;font-weight:400;letter-spacing:.2px;white-space:nowrap;cursor:pointer;transition:background .18s ease,color .18s ease}.tm-item:after{content:\"\";position:absolute;left:12px;right:12px;bottom:5px;height:3px;border-radius:3px;background:#fff;transform:scaleX(0);transform-origin:center;transition:transform .25s cubic-bezier(.4,0,.2,1)}.tm-item:hover{color:#fff}.tm-item:hover:after{transform:scaleX(1)}.tm-item.tm-item-active:after{transform:scaleX(1)}.tm-item-icon{font-size:19px!important;width:19px!important;height:19px!important;display:inline-flex!important;align-items:center;justify-content:center;color:inherit!important}.tm-item-caret{font-size:18px!important;width:18px!important;height:18px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-left:-2px;margin-right:-4px;color:#ffffffb3!important;transition:transform .2s ease}.tm-item:hover .tm-item-caret,.tm-item-active .tm-item-caret{color:#fff!important}.tm-actions{display:flex;align-items:center;gap:2px;flex-shrink:0;margin-left:auto}.tm-actions .mat-mdc-icon-button,.tm-action-btn{display:inline-flex!important;align-items:center!important;justify-content:center!important;color:#fff!important}.tm-actions mat-icon{color:#fff!important}.tm-divider-v{width:1px;height:24px;background:#ffffff38;margin:0 6px;flex-shrink:0}.tm-user-btn{display:inline-flex!important;align-items:center!important;gap:6px;height:40px;color:#fff!important;border-radius:8px;transition:background .18s ease}.tm-user-btn:hover{background:#ffffff1f}.tm-user-icon{font-size:24px!important;width:24px!important;height:24px!important;color:#fff!important}.tm-user-name{font-size:14px;font-weight:400;white-space:nowrap}::ng-deep .tm-submenu-panel .tm-sub-active{background:#0033aa14;font-weight:600;color:#03a}::ng-deep .tm-submenu-panel .tm-sub-active .mat-icon{color:#03a}@media (max-width: 991px){.tm-toggler{display:inline-flex}.tm-menu,.tm-actions{display:none;position:absolute;left:0;right:0;top:100%;flex-direction:column;align-items:stretch;background:#03a;padding:8px 12px;box-shadow:0 8px 18px #0003;z-index:1029}.tm-menu.tm-menu-open{display:flex}.tm-actions.tm-actions-open{display:flex;top:100%;border-top:1px solid rgba(255,255,255,.12)}.tm-item-wrap{width:100%}.tm-item-wrap:not(:first-child):before{width:100%;height:1px;margin:2px 0}.tm-item{width:100%;justify-content:flex-start;height:44px;margin:0}.tm-item:after{inset:8px auto 8px 0;width:4px;height:auto;transform:scaleY(0);transform-origin:center}.tm-item:hover:after,.tm-item.tm-item-active:after{transform:scaleY(1)}.tm-item-caret{margin-left:auto}.tm-divider-v{display:none}.tm-user-btn{justify-content:flex-start;width:100%}}\n"] }]
|
|
10263
10369
|
}], ctorParameters: () => [{ type: i1$2.Router }, { type: AuthService }, { type: StorageService }, { type: NotificationsService }, { type: i1$3.BreakpointObserver }, { type: DataServiceLib }, { type: i1.MatDialog }, { type: SubscriptionService }], propDecorators: { onWindowScroll: [{
|
|
10264
10370
|
type: HostListener,
|
|
10265
10371
|
args: ['window:scroll']
|
|
@@ -11754,11 +11860,11 @@ class TableComponent {
|
|
|
11754
11860
|
this.realTimeSubs.forEach(s => s.unsubscribe());
|
|
11755
11861
|
}
|
|
11756
11862
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i1$3.BreakpointObserver }, { token: i1.MatDialog }, { token: ButtonService }, { token: DialogService }, { token: TableConfigService }, { token: ConditionService }, { token: AuthService }, { token: SignalRService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
11757
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableComponent, isStandalone: false, selector: "spa-table", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab", nestingLevel: "nestingLevel" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" [isRealTime]=\"config.realTime\" [isConnected]=\"isSignalRConnected\"\r\n (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$1.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$1.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$1.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$1.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$1.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$1.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$1.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$1.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$1.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$1.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData", "isRealTime", "isConnected"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
11863
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableComponent, isStandalone: false, selector: "spa-table", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab", nestingLevel: "nestingLevel" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" [isRealTime]=\"config.realTime\" [isConnected]=\"isSignalRConnected\"\r\n (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%;max-height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column;min-height:0}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column;min-height:0}.dialog-container:not(.dialog-has-tables){height:auto}.dialog-container:not(.dialog-has-tables) .dialog-scroll-content{flex:0 1 auto;max-height:calc(92vh - 150px)!important}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$1.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$1.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$1.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$1.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$1.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$1.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$1.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$1.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$1.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$1.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData", "isRealTime", "isConnected"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
11758
11864
|
}
|
|
11759
11865
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableComponent, decorators: [{
|
|
11760
11866
|
type: Component,
|
|
11761
|
-
args: [{ selector: 'spa-table', standalone: false, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" [isRealTime]=\"config.realTime\" [isConnected]=\"isSignalRConnected\"\r\n (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
|
|
11867
|
+
args: [{ selector: 'spa-table', standalone: false, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" [isRealTime]=\"config.realTime\" [isConnected]=\"isSignalRConnected\"\r\n (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%;max-height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column;min-height:0}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column;min-height:0}.dialog-container:not(.dialog-has-tables){height:auto}.dialog-container:not(.dialog-has-tables) .dialog-scroll-content{flex:0 1 auto;max-height:calc(92vh - 150px)!important}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
|
|
11762
11868
|
}], ctorParameters: () => [{ type: DataServiceLib }, { type: MessageService }, { type: i1$3.BreakpointObserver }, { type: i1.MatDialog }, { type: ButtonService }, { type: DialogService }, { type: TableConfigService }, { type: ConditionService }, { type: AuthService }, { type: SignalRService }], propDecorators: { tablePaginator: [{
|
|
11763
11869
|
type: ViewChild,
|
|
11764
11870
|
args: ['tablePaginator', { static: false }]
|
|
@@ -13221,11 +13327,11 @@ class DetailsDialog {
|
|
|
13221
13327
|
}
|
|
13222
13328
|
}
|
|
13223
13329
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialog, deps: [{ token: i1$3.BreakpointObserver }, { token: LoaderService }, { token: DataServiceLib }, { token: MessageService }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: ButtonService }, { token: DialogService }, { token: AuthService }, { token: TableConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
13224
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialog, isStandalone: false, selector: "spa-detailsDialog", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Use unified spa-tabs with nestingLevel control \u2014 tabs hidden when nestingLevel >= 2 -->\r\n <spa-tabs\r\n *ngIf=\"showTabs && tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n [nestingLevel]=\"nestingLevel + 1\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i5.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$2.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "parentDetails", "nestingLevel"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
13330
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialog, isStandalone: false, selector: "spa-detailsDialog", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<!-- Changed: dialog-has-tables mirrors the service's hasTables (tableConfigs?.length)\r\n which decides height '90%' vs 'auto' \u2014 used to scope scroll behaviour per case -->\r\n<div class=\"dialog-container\" [class.dialog-has-tables]=\"detailsConfig.tableConfigs?.length > 0\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Use unified spa-tabs with nestingLevel control \u2014 tabs hidden when nestingLevel >= 2 -->\r\n <spa-tabs\r\n *ngIf=\"showTabs && tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n [nestingLevel]=\"nestingLevel + 1\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%;max-height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column;min-height:0}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column;min-height:0}.dialog-container:not(.dialog-has-tables){height:auto}.dialog-container:not(.dialog-has-tables) .dialog-scroll-content{flex:0 1 auto;max-height:calc(92vh - 150px)!important}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i5.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$2.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "parentDetails", "nestingLevel"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
13225
13331
|
}
|
|
13226
13332
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialog, decorators: [{
|
|
13227
13333
|
type: Component,
|
|
13228
|
-
args: [{ selector: 'spa-detailsDialog', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Use unified spa-tabs with nestingLevel control \u2014 tabs hidden when nestingLevel >= 2 -->\r\n <spa-tabs\r\n *ngIf=\"showTabs && tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n [nestingLevel]=\"nestingLevel + 1\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
|
|
13334
|
+
args: [{ selector: 'spa-detailsDialog', standalone: false, template: "<!-- Changed: dialog-has-tables mirrors the service's hasTables (tableConfigs?.length)\r\n which decides height '90%' vs 'auto' \u2014 used to scope scroll behaviour per case -->\r\n<div class=\"dialog-container\" [class.dialog-has-tables]=\"detailsConfig.tableConfigs?.length > 0\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Use unified spa-tabs with nestingLevel control \u2014 tabs hidden when nestingLevel >= 2 -->\r\n <spa-tabs\r\n *ngIf=\"showTabs && tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n [nestingLevel]=\"nestingLevel + 1\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%;max-height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column;min-height:0}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column;min-height:0}.dialog-container:not(.dialog-has-tables){height:auto}.dialog-container:not(.dialog-has-tables) .dialog-scroll-content{flex:0 1 auto;max-height:calc(92vh - 150px)!important}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
|
|
13229
13335
|
}], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: LoaderService }, { type: DataServiceLib }, { type: MessageService }, { type: i1.MatDialogRef }, { type: DetailsDialogConfig, decorators: [{
|
|
13230
13336
|
type: Inject,
|
|
13231
13337
|
args: [MAT_DIALOG_DATA]
|
|
@@ -14183,11 +14289,11 @@ class SpaLandingComponent {
|
|
|
14183
14289
|
s.setProperty('--lp-secondary-light', c.secondaryLight);
|
|
14184
14290
|
}
|
|
14185
14291
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SpaLandingComponent, deps: [{ token: i1$2.Router }, { token: AuthService }, { token: i0.ElementRef }, { token: i1$5.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
14186
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SpaLandingComponent, isStandalone: false, selector: "spa-landing", inputs: { config: "config" }, host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0, template: "<!-- ===== NAVBAR ===== -->\n<nav class=\"lp-navbar\" [class.lp-navbar-scrolled]=\"navScrolled\">\n <div class=\"lp-container lp-nav-inner\">\n <!-- Brand: custom SVG override OR default img+name -->\n <div class=\"lp-nav-brand\" (click)=\"scrollToSection('lp-hero')\">\n <ng-container *ngIf=\"config.navBrand; else defaultNavBrand\">\n <span class=\"lp-nav-brand-svg\" [innerHTML]=\"safeNavBrandSvg\"></span>\n <div *ngIf=\"config.navBrand.wordmarkLine1\" class=\"lp-nav-brand-wordmark\">\n <span class=\"lp-nav-wordmark-line1\">{{ config.navBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.navBrand.wordmarkLine2\" class=\"lp-nav-wordmark-line2\">{{ config.navBrand.wordmarkLine2 }}</span>\n </div>\n </ng-container>\n <ng-template #defaultNavBrand>\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-nav-logo\">\n <span class=\"lp-nav-brand-text\">{{ config.appName }}</span>\n </ng-template>\n </div>\n <!-- Desktop nav links -->\n <div class=\"lp-nav-links\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n </div>\n <!-- Mobile menu overlay -->\n <div class=\"lp-mobile-menu\" [class.lp-mobile-menu-open]=\"mobileMenuOpen\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n <div class=\"lp-mobile-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n </div>\n <!-- Desktop auth -->\n <div class=\"lp-nav-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary lp-btn-sm\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n <!-- Hamburger for mobile -->\n <button class=\"lp-hamburger\" (click)=\"toggleMobileMenu()\" [class.lp-hamburger-open]=\"mobileMenuOpen\">\n <span></span><span></span><span></span>\n </button>\n </div>\n</nav>\n\n<!-- ===== HERO ===== -->\n<section id=\"lp-hero\" class=\"lp-hero-bg\" [class.lp-hero-split]=\"config.hero.layout === 'split'\">\n <!-- Optional faded watermark SVG (e.g. company logo) -->\n <!-- Changed: wrapper div gets Angular scoping attribute; inner div holds innerHTML so svg size CSS applies to scoped .lp-hero-watermark-inner -->\n <div *ngIf=\"config.hero.watermarkSvg\" class=\"lp-hero-watermark\" aria-hidden=\"true\">\n <div class=\"lp-hero-watermark-inner\" [innerHTML]=\"safeHeroWatermarkSvg\"></div>\n </div>\n\n <div class=\"lp-container lp-hero-content\">\n <!-- Left / centered column: brand, headline, CTAs -->\n <div class=\"lp-hero-main\">\n <!-- App brand: logo + name + decorative divider -->\n <div class=\"lp-hero-brand\">\n <div class=\"lp-hero-brand-logo\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-hero-brand-icon\">\n <span class=\"lp-hero-brand-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-hero-divider\" aria-hidden=\"true\">\n <span class=\"lp-hero-divider-line\"></span>\n <span class=\"lp-hero-divider-dot\"></span>\n <span class=\"lp-hero-divider-line\"></span>\n </div>\n </div>\n <!-- Hero text: headline, subtitle, CTAs, trust signals -->\n <div class=\"lp-hero-text\">\n <div *ngIf=\"config.hero.badge\" class=\"lp-hero-badge\">{{ config.hero.badge }}</div>\n <h1>{{ config.hero.headline }} <span class=\"lp-gradient-text\">{{ config.hero.gradientText }}</span></h1>\n <p class=\"lp-hero-subtitle\">{{ config.hero.subtitle }}</p>\n <div class=\"lp-hero-ctas\">\n <button class=\"lp-btn lp-btn-primary lp-btn-lg\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">\n {{ config.hero.primaryCTA }}\n </button>\n <button class=\"lp-btn lp-btn-outline lp-btn-lg\" (click)=\"scrollToSection(config.hero.secondaryCTASection)\">\n {{ config.hero.secondaryCTA }}\n </button>\n </div>\n <!-- Trust signals with checkmark SVG icons -->\n <div class=\"lp-trust-signals\">\n <div *ngFor=\"let signal of config.hero.trustSignals\" class=\"lp-trust-item\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M20 6L9 17l-5-5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n <span>{{ signal }}</span>\n </div>\n </div>\n </div>\n </div>\n <!-- App visual: always rendered when projected content exists (stacked below hero text) -->\n <!-- Changed: removed *ngIf so visual always renders below text; layout is always stacked/column -->\n <div class=\"lp-hero-visual\">\n <ng-content select=\"[lpHeroVisual]\"></ng-content>\n </div>\n </div>\n</section>\n\n<!-- ===== METRICS (optional) ===== -->\n<section *ngIf=\"config.metrics\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-metrics-grid\">\n <div *ngFor=\"let metric of config.metrics\" class=\"lp-metric-card\">\n <div class=\"lp-metric-value\">{{ metric.value }}</div>\n <div class=\"lp-metric-label\">{{ metric.label }}</div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FEATURES ===== -->\n<section id=\"lp-features\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.features.title }}</h2>\n <p>{{ config.features.subtitle }}</p>\n </div>\n <div class=\"lp-features-grid\">\n <div *ngFor=\"let feature of config.features.items\" class=\"lp-feature-card\">\n <!-- Icon tint: hex + '20' = ~12% opacity background -->\n <div class=\"lp-feature-icon\"\n [style.background]=\"iconBg(feature.color)\"\n [style.color]=\"feature.color\">\n <span class=\"material-icons\">{{ feature.icon }}</span>\n </div>\n <h3 class=\"lp-feature-title\">{{ feature.title }}</h3>\n <p class=\"lp-feature-desc\">{{ feature.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== WORKFLOW (optional) ===== -->\n<section *ngIf=\"config.workflow\" id=\"lp-workflow\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.workflow!.title }}</h2>\n <p *ngIf=\"config.workflow!.subtitle\">{{ config.workflow!.subtitle }}</p>\n </div>\n\n <!-- Road metaphor layout: horizontal road line with waypoints -->\n <div *ngIf=\"config.workflow!.style === 'road'\" class=\"lp-workflow-road\">\n <div class=\"lp-road-line\"></div>\n <div *ngFor=\"let step of config.workflow!.steps\" class=\"lp-waypoint\">\n <div class=\"lp-waypoint-marker\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <div class=\"lp-waypoint-number\">Step {{ step.number }}</div>\n <div class=\"lp-waypoint-title\">{{ step.title }}</div>\n <div class=\"lp-waypoint-desc\">{{ step.description }}</div>\n </div>\n </div>\n\n <!-- Default steps grid layout -->\n <div *ngIf=\"!config.workflow!.style || config.workflow!.style === 'steps'\" class=\"lp-steps-grid\">\n <div *ngFor=\"let step of config.workflow!.steps; let i = index\" class=\"lp-step-card\">\n <div class=\"lp-step-number\">{{ step.number }}</div>\n <div class=\"lp-step-icon\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <h3 class=\"lp-step-title\">{{ step.title }}</h3>\n <p class=\"lp-step-desc\">{{ step.description }}</p>\n <!-- Connector line between steps, not after last -->\n <div *ngIf=\"i < config.workflow!.steps.length - 1\" class=\"lp-step-connector\"></div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== CUSTOM SECTION SLOT ===== -->\n<!-- Consumer projects app-specific sections here (fleet board, benefits rows, etc.)\n using [lpCustomSection] attribute on a wrapper element -->\n<ng-content select=\"[lpCustomSection]\"></ng-content>\n\n<!-- ===== MODULES (optional) ===== -->\n<section *ngIf=\"config.modules\" id=\"lp-modules\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.modules!.title }}</h2>\n <p>{{ config.modules!.subtitle }}</p>\n </div>\n <div class=\"lp-modules-grid\">\n <div *ngFor=\"let group of config.modules!.groups\" class=\"lp-module-group\">\n <div class=\"lp-module-group-label\">{{ group.category }}</div>\n <div class=\"lp-module-tiles\">\n <div *ngFor=\"let tile of group.tiles\" class=\"lp-module-tile\">\n <span class=\"material-icons\">{{ tile.icon }}</span>\n <span>{{ tile.name }}</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== SECURITY (optional \u2014 dark background) ===== -->\n<section *ngIf=\"config.security\" class=\"lp-section lp-section-dark lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header lp-header-light\">\n <h2>{{ config.security!.title }}</h2>\n <p>{{ config.security!.subtitle }}</p>\n </div>\n <div class=\"lp-security-grid\">\n <div *ngFor=\"let item of config.security!.items\" class=\"lp-security-card\">\n <div class=\"lp-security-icon\">\n <span class=\"material-icons\">{{ item.icon }}</span>\n </div>\n <h3>{{ item.title }}</h3>\n <p>{{ item.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== TESTIMONIALS (optional) ===== -->\n<section *ngIf=\"config.testimonials\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.testimonials!.title }}</h2>\n <p *ngIf=\"config.testimonials!.subtitle\">{{ config.testimonials!.subtitle }}</p>\n </div>\n <div class=\"lp-testimonials-grid\">\n <div *ngFor=\"let item of config.testimonials!.items\" class=\"lp-testimonial-card\">\n <div class=\"lp-stars\">\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n </div>\n <p class=\"lp-testimonial-quote\">\"{{ item.quote }}\"</p>\n <div class=\"lp-testimonial-author\">\n <div class=\"lp-author-avatar\">{{ item.authorInitials }}</div>\n <div>\n <div class=\"lp-author-name\">{{ item.authorName }}</div>\n <div class=\"lp-author-role\">{{ item.authorRole }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== PRICING (optional) ===== -->\n<section *ngIf=\"config.pricing\" id=\"lp-pricing\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.pricing!.title }}</h2>\n <p *ngIf=\"config.pricing!.subtitle\">{{ config.pricing!.subtitle }}</p>\n </div>\n <div class=\"lp-pricing-grid\">\n <div *ngFor=\"let plan of config.pricing!.plans\"\n class=\"lp-pricing-card\"\n [class.lp-pricing-popular]=\"plan.popular\">\n <div *ngIf=\"plan.popular\" class=\"lp-popular-badge\">Most Popular</div>\n <div class=\"lp-pricing-header\">\n <h3>{{ plan.name }}</h3>\n <div class=\"lp-pricing-price\">\n <span class=\"lp-price-amount\">{{ plan.price }}</span>\n <span *ngIf=\"plan.period\" class=\"lp-price-period\">{{ plan.period }}</span>\n </div>\n <p class=\"lp-pricing-desc\">{{ plan.description }}</p>\n </div>\n <ul class=\"lp-pricing-features\">\n <li *ngFor=\"let feature of plan.features\">{{ feature }}</li>\n </ul>\n <!-- Popular plans get primary button, others get outline -->\n <button *ngIf=\"plan.popular\"\n class=\"lp-btn lp-btn-primary lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n <button *ngIf=\"!plan.popular\"\n class=\"lp-btn lp-btn-outline lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FINAL CTA BANNER (optional) ===== -->\n<section *ngIf=\"config.finalCta\" class=\"lp-final-cta lp-animate\">\n <div class=\"lp-container lp-cta-content\">\n <h2>{{ config.finalCta!.headline }}</h2>\n <p>{{ config.finalCta!.subtitle }}</p>\n <div class=\"lp-cta-buttons\">\n <button class=\"lp-btn lp-btn-white lp-btn-lg\"\n (click)=\"navigateTo(config.finalCta!.primaryCTARoute || 'signup')\">\n {{ config.finalCta!.primaryCTA }}\n </button>\n <button *ngIf=\"config.finalCta!.secondaryCTA\"\n class=\"lp-btn lp-btn-outline-white lp-btn-lg\"\n (click)=\"scrollToSection(config.finalCta!.secondaryCTASection || '')\">\n {{ config.finalCta!.secondaryCTA }}\n </button>\n </div>\n </div>\n</section>\n\n<!-- ===== FOOTER ===== -->\n<footer class=\"lp-footer\" id=\"lp-footer\">\n <div class=\"lp-container\">\n\n <!-- Rich multi-column footer when footerColumns provided -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length; else simpleFooter\" class=\"lp-footer-grid\">\n <!-- Brand column -->\n <div class=\"lp-footer-brand-col\">\n <!-- Custom SVG footer brand OR default img+name -->\n <ng-container *ngIf=\"config.footerBrand; else defaultFooterBrand\">\n <div class=\"lp-footer-brand-custom\">\n <span class=\"lp-footer-brand-svg\" [innerHTML]=\"safeFooterBrandSvg\"></span>\n <div *ngIf=\"config.footerBrand.wordmarkLine1\" class=\"lp-footer-brand-wordmark\">\n <span class=\"lp-footer-wordmark-line1\">{{ config.footerBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.footerBrand.wordmarkLine2\" class=\"lp-footer-wordmark-line2\">{{ config.footerBrand.wordmarkLine2 }}</span>\n </div>\n </div>\n </ng-container>\n <ng-template #defaultFooterBrand>\n <div class=\"lp-footer-brand-default\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n </ng-template>\n <p *ngIf=\"config.footerTagline\" class=\"lp-footer-tagline\">{{ config.footerTagline }}</p>\n </div>\n <!-- Link columns -->\n <div *ngFor=\"let col of config.footerColumns\" class=\"lp-footer-col\">\n <h4>{{ col.title }}</h4>\n <a *ngFor=\"let link of col.links\"\n (click)=\"link.section ? scrollToSection(link.section) : (link.route ? navigateTo(link.route) : null)\">\n {{ link.label }}\n </a>\n </div>\n </div>\n\n <!-- Simple single-row footer (default) -->\n <ng-template #simpleFooter>\n <div class=\"lp-footer-inner\">\n <div class=\"lp-footer-brand\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-footer-links\">\n <a (click)=\"navigateTo('login')\">Log in</a>\n <a (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Sign up</a>\n </div>\n <div class=\"lp-footer-copy\">\n © {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.\n </div>\n </div>\n </ng-template>\n\n <!-- Footer bottom bar (shown for both layouts) -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length\" class=\"lp-footer-bottom\">\n <span>© {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.</span>\n </div>\n </div>\n</footer>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap\";:host{--lp-primary: #1E3A5F;--lp-primary-dark: #152C4A;--lp-primary-light: #EBF0F7;--lp-secondary: #F59E0B;--lp-secondary-light:#FEF3C7;--lp-dark: #0D1117;--lp-gray-800: #1F2937;--lp-gray-700: #374151;--lp-gray-600: #4B5563;--lp-gray-400: #9CA3AF;--lp-gray-200: #E5E7EB;--lp-gray-100: #F3F4F6;--lp-gray-50: #F9FAFB;--lp-white: #FFFFFF;--lp-radius: 12px;--lp-radius-sm: 8px;--lp-radius-lg: 20px;--lp-radius-xl: 28px;--lp-shadow-xs: 0 1px 2px rgba(0,0,0,.05);--lp-shadow: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.05);--lp-shadow-md: 0 4px 8px rgba(0,0,0,.06), 0 2px 4px rgba(0,0,0,.04);--lp-shadow-lg: 0 12px 28px rgba(0,0,0,.1), 0 4px 8px rgba(0,0,0,.06);--lp-shadow-xl: 0 20px 40px rgba(0,0,0,.12), 0 8px 16px rgba(0,0,0,.07);--lp-ease: cubic-bezier(.4, 0, .2, 1);--lp-spring: cubic-bezier(.34, 1.56, .64, 1);display:block;font-family:Outfit,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--lp-gray-700);line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased}.lp-container{max-width:1200px;margin:0 auto;padding:0 32px}.lp-navbar{position:fixed;top:0;left:0;right:0;z-index:1000;padding:18px 0;transition:background .3s var(--lp-ease),padding .3s var(--lp-ease),box-shadow .3s var(--lp-ease)}.lp-navbar-scrolled{background:#ffffffeb;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);box-shadow:0 1px #0000000f,0 2px 8px #0000000a;padding:12px 0}.lp-nav-inner{display:flex;align-items:center;justify-content:space-between}.lp-nav-brand{display:flex;align-items:center;gap:10px;cursor:pointer;text-decoration:none}.lp-nav-logo{width:30px;height:30px;border-radius:7px;object-fit:contain}.lp-nav-brand-text{font-size:18px;font-weight:700;color:var(--lp-primary);letter-spacing:-.01em}.lp-nav-links{display:flex;align-items:center;gap:36px}.lp-nav-links a{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none;letter-spacing:.01em}.lp-nav-links a:hover{color:var(--lp-primary)}.lp-nav-auth{display:flex;align-items:center;gap:12px}.lp-nav-login{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none}.lp-nav-login:hover{color:var(--lp-primary)}.lp-hamburger{display:none;flex-direction:column;gap:5px;background:none;border:none;cursor:pointer;padding:4px;z-index:1001}.lp-hamburger span{display:block;width:22px;height:2px;background:var(--lp-gray-700);border-radius:2px;transition:transform .3s var(--lp-ease),opacity .3s}.lp-hamburger-open span:nth-child(1){transform:translateY(7px) rotate(45deg)}.lp-hamburger-open span:nth-child(2){opacity:0}.lp-hamburger-open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.lp-mobile-menu{display:none}.lp-mobile-auth{display:flex;flex-direction:column;gap:12px;margin-top:24px;padding-top:24px;border-top:1px solid var(--lp-gray-200)}.lp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:Outfit,sans-serif;font-size:14px;font-weight:600;padding:10px 22px;border-radius:var(--lp-radius-sm);border:none;cursor:pointer;transition:all .25s var(--lp-ease);text-decoration:none;white-space:nowrap;letter-spacing:.01em}.lp-btn-primary{background:var(--lp-primary);color:var(--lp-white);box-shadow:0 1px 3px #00000026,inset 0 1px #ffffff1f}.lp-btn-primary:hover{background:var(--lp-primary-dark);transform:translateY(-1px);box-shadow:0 6px 16px #0003,inset 0 1px #ffffff1f}.lp-btn-primary:active{transform:translateY(0)}.lp-btn-outline{background:transparent;color:var(--lp-primary);border:1.5px solid var(--lp-primary)}.lp-btn-outline:hover{background:var(--lp-primary);color:var(--lp-white);transform:translateY(-1px);box-shadow:0 4px 12px #00000026}.lp-btn-sm{padding:7px 16px;font-size:13px}.lp-btn-lg{padding:14px 32px;font-size:15px;border-radius:var(--lp-radius)}.lp-btn-block{width:100%}.lp-hero-bg{position:relative;overflow:hidden;padding:140px 0 100px;background:radial-gradient(ellipse 80% 60% at 60% -10%,color-mix(in srgb,var(--lp-primary) 8%,transparent) 0%,transparent 70%),radial-gradient(ellipse 60% 50% at -10% 80%,color-mix(in srgb,var(--lp-secondary) 6%,transparent) 0%,transparent 70%),linear-gradient(160deg,var(--lp-primary-light) 0%,#FAFBFF 45%,var(--lp-secondary-light) 100%);min-height:100vh;display:flex;align-items:center}.lp-hero-bg:before{content:\"\";position:absolute;inset:0;background-image:radial-gradient(circle,rgba(0,0,0,.04) 1px,transparent 1px);background-size:32px 32px;pointer-events:none;-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%);mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%)}.lp-hero-bg:after{content:\"\";position:absolute;width:700px;height:700px;border-radius:50%;background:color-mix(in srgb,var(--lp-primary) 5%,transparent);top:-200px;right:-150px;filter:blur(100px);pointer-events:none}.lp-hero-content{position:relative;z-index:1;text-align:center;width:100%}.lp-hero-badge{display:inline-flex;align-items:center;gap:6px;background:color-mix(in srgb,var(--lp-primary) 10%,transparent);color:var(--lp-primary);border:1px solid color-mix(in srgb,var(--lp-primary) 20%,transparent);border-radius:100px;padding:6px 16px;font-size:13px;font-weight:600;margin-bottom:20px;letter-spacing:.02em}.lp-hero-brand{margin-bottom:20px;text-align:center}.lp-hero-brand-logo{display:inline-flex;align-items:center;gap:14px;margin-bottom:12px}.lp-hero-brand-icon{height:52px;width:52px;border-radius:14px;object-fit:contain;box-shadow:0 4px 12px #0000001f}.lp-hero-brand-name{font-size:38px;font-weight:800;color:var(--lp-primary);letter-spacing:-.03em;font-family:Outfit,sans-serif}.lp-hero-divider{display:flex;align-items:center;justify-content:center;gap:10px}.lp-hero-divider-line{display:block;height:1px;width:40px;background:linear-gradient(90deg,transparent,var(--lp-primary),transparent);opacity:.3}.lp-hero-divider-dot{display:block;height:5px;width:5px;border-radius:50%;background:var(--lp-secondary);opacity:.8}.lp-hero-text{max-width:760px;margin:0 auto 64px}.lp-hero-text h1{font-family:Outfit,sans-serif;font-size:68px;font-weight:900;line-height:1.05;color:var(--lp-dark);margin:0 0 24px;letter-spacing:-.03em}.lp-gradient-text{background:linear-gradient(135deg,var(--lp-primary) 0%,var(--lp-secondary) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.lp-hero-subtitle{font-size:19px;color:var(--lp-gray-600);line-height:1.65;font-weight:400;max-width:560px;margin:0 auto 36px}.lp-hero-ctas{display:flex;align-items:center;justify-content:center;gap:14px;margin-bottom:32px;flex-wrap:wrap}.lp-trust-signals{display:flex;align-items:center;justify-content:center;gap:28px;flex-wrap:wrap}.lp-trust-item{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:500;color:var(--lp-gray-500, #6B7280)}.lp-trust-item svg{color:var(--lp-primary);flex-shrink:0}.lp-section{padding:100px 0;background:var(--lp-white)}.lp-section-alt{background:var(--lp-gray-50)}.lp-section-header{text-align:center;margin-bottom:64px}.lp-section-header h2{font-family:Outfit,sans-serif;font-size:42px;font-weight:800;color:var(--lp-dark);margin:0 0 14px;letter-spacing:-.025em;line-height:1.15}.lp-section-header p{font-size:17px;color:var(--lp-gray-600);max-width:540px;margin:0 auto;line-height:1.65}.lp-features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.lp-feature-card{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:36px 28px;transition:transform .25s var(--lp-ease),box-shadow .25s var(--lp-ease);border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);position:relative;overflow:hidden}.lp-feature-card:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--lp-primary),var(--lp-secondary));transform:scaleX(0);transform-origin:left;transition:transform .3s var(--lp-ease);border-radius:3px 3px 0 0}.lp-feature-card:hover{transform:translateY(-5px);box-shadow:var(--lp-shadow-xl);border-color:transparent}.lp-feature-card:hover:before{transform:scaleX(1)}.lp-feature-icon{width:52px;height:52px;border-radius:var(--lp-radius-sm);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.lp-feature-icon .material-icons{font-size:26px}.lp-feature-title{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-dark);margin:0 0 10px;letter-spacing:-.01em}.lp-feature-desc{font-size:14px;color:var(--lp-gray-600);line-height:1.65;margin:0}.lp-steps-grid{display:flex;align-items:flex-start;justify-content:space-between;position:relative;gap:16px}.lp-step-card{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;padding:0 8px}.lp-step-number{font-size:11px;font-weight:700;color:var(--lp-secondary);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px}.lp-step-icon{width:56px;height:56px;border-radius:50%;background:var(--lp-primary);display:flex;align-items:center;justify-content:center;margin-bottom:18px;box-shadow:0 4px 14px color-mix(in srgb,var(--lp-primary) 30%,transparent);border:3px solid var(--lp-white);position:relative;z-index:1}.lp-step-icon .material-icons{font-size:24px;color:var(--lp-white)}.lp-step-title{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--lp-dark);margin:0 0 8px;letter-spacing:-.01em}.lp-step-desc{font-size:13px;color:var(--lp-gray-600);line-height:1.55;margin:0}.lp-step-connector{position:absolute;top:40px;left:60%;width:80%;height:2px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 0px,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 10px,transparent 10px,transparent 18px);pointer-events:none;z-index:0}.lp-metrics-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:1px solid var(--lp-gray-200);border-radius:var(--lp-radius-lg);overflow:hidden;background:var(--lp-white);box-shadow:var(--lp-shadow)}.lp-metric-card{text-align:center;padding:40px 24px;border-right:1px solid var(--lp-gray-200);transition:background .2s}.lp-metric-card:last-child{border-right:none}.lp-metric-card:hover{background:var(--lp-gray-50)}.lp-metric-value{font-family:Outfit,sans-serif;font-size:48px;font-weight:900;color:var(--lp-primary);line-height:1;letter-spacing:-.03em;margin-bottom:8px}.lp-metric-label{font-size:14px;font-weight:500;color:var(--lp-gray-600)}.lp-modules-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px}.lp-module-group{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:28px 28px 24px;border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);transition:box-shadow .2s}.lp-module-group:hover{box-shadow:var(--lp-shadow-md)}.lp-module-group-label{font-family:Outfit,sans-serif;font-size:11px;font-weight:700;color:var(--lp-primary);margin-bottom:16px;text-transform:uppercase;letter-spacing:1px}.lp-module-tiles{display:flex;flex-wrap:wrap;gap:8px}.lp-module-tile{display:inline-flex;align-items:center;gap:6px;background:var(--lp-gray-50);border:1px solid var(--lp-gray-200);padding:7px 13px;border-radius:100px;font-size:12px;font-weight:600;color:var(--lp-gray-700);transition:all .2s var(--lp-ease);cursor:default}.lp-module-tile:hover{background:color-mix(in srgb,var(--lp-primary) 6%,transparent);border-color:color-mix(in srgb,var(--lp-primary) 25%,transparent);color:var(--lp-primary);transform:translateY(-1px)}.lp-module-tile .material-icons{font-size:15px;color:var(--lp-primary)}.lp-footer{background:var(--lp-dark);padding:44px 0 36px;border-top:1px solid rgba(255,255,255,.04)}.lp-footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:20px}.lp-footer-brand{display:flex;align-items:center;gap:10px}.lp-footer-logo{width:30px;height:30px;border-radius:7px;object-fit:contain;opacity:.9}.lp-footer-name{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-white);letter-spacing:-.01em}.lp-footer-links{display:flex;gap:28px}.lp-footer-links a{font-size:14px;font-weight:500;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-links a:hover{color:var(--lp-white)}.lp-footer-copy{font-size:13px;color:#ffffff59;width:100%;text-align:center;padding-top:20px;margin-top:4px;border-top:1px solid rgba(255,255,255,.06)}.lp-animate{opacity:0;transform:translateY(28px);transition:opacity .65s var(--lp-ease),transform .65s var(--lp-ease)}.lp-visible{opacity:1;transform:translateY(0)}.lp-visible .lp-feature-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-feature-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-feature-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-feature-card:nth-child(4){transition-delay:.18s}.lp-visible .lp-feature-card:nth-child(5){transition-delay:.24s}.lp-visible .lp-feature-card:nth-child(6){transition-delay:.3s}.lp-visible .lp-step-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-step-card:nth-child(2){transition-delay:80ms}.lp-visible .lp-step-card:nth-child(3){transition-delay:.16s}.lp-visible .lp-step-card:nth-child(4){transition-delay:.24s}.lp-visible .lp-metric-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-metric-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-metric-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-metric-card:nth-child(4){transition-delay:.18s}@media (max-width: 1024px){.lp-hero-text h1{font-size:52px}.lp-section-header h2{font-size:36px}.lp-features-grid,.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card:nth-child(2){border-right:none}.lp-metrics-grid .lp-metric-card:nth-child(3){border-top:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:nth-child(4){border-top:1px solid var(--lp-gray-200)}.lp-steps-grid{flex-wrap:wrap;justify-content:center;gap:40px}.lp-step-connector{display:none}}@media (max-width: 767px){.lp-container{padding:0 20px}.lp-nav-links{display:none}.lp-hamburger{display:flex}.lp-mobile-auth{display:none}.lp-mobile-menu{display:none;position:fixed;inset:0;background:#fffffff7;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;align-items:center;justify-content:center;gap:28px;z-index:999}.lp-mobile-menu-open{display:flex}.lp-mobile-menu a{font-size:22px;font-weight:700;color:var(--lp-dark);cursor:pointer;text-decoration:none;letter-spacing:-.01em}.lp-hero-bg{padding:100px 0 60px;min-height:auto}.lp-hero-text h1{font-size:38px}.lp-hero-brand-name{font-size:28px}.lp-hero-subtitle{font-size:16px}.lp-hero-ctas{flex-direction:column}.lp-hero-ctas .lp-btn{width:100%;justify-content:center}.lp-trust-signals{gap:16px}.lp-section{padding:72px 0}.lp-section-header{margin-bottom:40px}.lp-section-header h2{font-size:28px}.lp-features-grid{grid-template-columns:1fr}.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card{border-right:none;border-bottom:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:last-child{border-bottom:none}.lp-metric-value{font-size:36px}.lp-modules-grid{grid-template-columns:1fr}.lp-steps-grid{flex-direction:column;align-items:center;gap:32px}.lp-step-connector{display:none}.lp-footer-inner{flex-direction:column;align-items:center;text-align:center}}.lp-nav-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-nav-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-nav-brand-wordmark{display:flex;flex-direction:column;line-height:1.1;margin-left:8px}.lp-nav-wordmark-line1{font-size:15px;font-weight:800;color:#1a3a7a;letter-spacing:-.01em;font-family:Outfit,sans-serif}.lp-nav-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-hero-bg{position:relative}.lp-hero-watermark{position:absolute;inset:0;display:flex;align-items:flex-start;justify-content:center;padding-top:40px;pointer-events:none;z-index:1}.lp-hero-watermark-inner{width:700px;height:700px;opacity:.04;overflow:hidden;flex-shrink:0}@media (min-width: 768px){.lp-hero-watermark-inner{width:800px;height:800px}}.lp-hero-content{display:flex;flex-direction:column;align-items:center}.lp-hero-main{z-index:1;width:100%}.lp-hero-visual{position:relative;z-index:1;width:100%;max-width:900px;margin:48px auto 0}.lp-workflow-road{position:relative;display:flex;justify-content:space-between;align-items:flex-start;padding:48px 0 24px;gap:16px}.lp-road-line{position:absolute;top:72px;left:6%;right:6%;height:3px;background:linear-gradient(90deg,var(--lp-primary-light),var(--lp-primary),var(--lp-primary-light));border-radius:2px;opacity:.4}.lp-waypoint{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;z-index:1}.lp-waypoint-marker{width:52px;height:52px;border-radius:50%;background:var(--lp-primary);color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:12px;box-shadow:0 4px 16px color-mix(in srgb,var(--lp-primary) 35%,transparent);transition:transform .25s cubic-bezier(.34,1.56,.64,1)}.lp-waypoint:hover .lp-waypoint-marker{transform:translateY(-4px) scale(1.08)}.lp-waypoint-marker .material-icons{font-size:22px}.lp-waypoint-number{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--lp-primary);margin-bottom:6px;opacity:.7}.lp-waypoint-title{font-size:14px;font-weight:700;color:var(--lp-dark);margin-bottom:6px;font-family:Outfit,sans-serif}.lp-waypoint-desc{font-size:12px;color:var(--lp-gray-600);line-height:1.5;max-width:140px}.lp-section-dark{background:var(--lp-dark);color:#fff}.lp-section-dark.lp-section{padding:96px 0}.lp-header-light h2{color:#fff}.lp-header-light p{color:#ffffffa6}.lp-security-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}.lp-security-card{background:#ffffff0d;border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:32px 28px;transition:background .25s ease}.lp-security-card:hover{background:#ffffff14}.lp-security-icon{width:52px;height:52px;border-radius:14px;background:#ffffff1a;display:flex;align-items:center;justify-content:center;margin-bottom:20px;color:#fff}.lp-security-icon .material-icons{font-size:24px}.lp-security-card h3{font-size:18px;font-weight:700;color:#fff;margin:0 0 12px;font-family:Outfit,sans-serif}.lp-security-card p{font-size:14px;color:#fff9;line-height:1.65;margin:0}.lp-testimonials-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px}.lp-testimonial-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:32px 28px;display:flex;flex-direction:column;gap:16px;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-testimonial-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-2px)}.lp-stars{display:flex;gap:2px;color:var(--lp-secondary)}.lp-stars .material-icons{font-size:18px}.lp-testimonial-quote{font-size:15px;color:var(--lp-gray-700);line-height:1.65;font-style:italic;margin:0;flex:1}.lp-testimonial-author{display:flex;align-items:center;gap:12px}.lp-author-avatar{width:44px;height:44px;border-radius:50%;background:var(--lp-primary);color:#fff;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:Outfit,sans-serif}.lp-author-name{font-size:14px;font-weight:700;color:var(--lp-dark)}.lp-author-role{font-size:12px;color:var(--lp-gray-400)}.lp-pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;align-items:start}.lp-pricing-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:36px 28px 28px;position:relative;display:flex;flex-direction:column;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-pricing-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-3px)}.lp-pricing-popular{border-color:var(--lp-primary);box-shadow:0 0 0 2px var(--lp-primary);transform:scale(1.03)}.lp-pricing-popular:hover{transform:scale(1.03) translateY(-3px)}.lp-popular-badge{position:absolute;top:-12px;left:50%;transform:translate(-50%);background:var(--lp-primary);color:#fff;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:4px 14px;border-radius:100px;white-space:nowrap;font-family:Outfit,sans-serif}.lp-pricing-header{margin-bottom:24px}.lp-pricing-header h3{font-size:20px;font-weight:800;color:var(--lp-dark);margin:0 0 12px;font-family:Outfit,sans-serif}.lp-pricing-price{display:flex;align-items:baseline;gap:4px;margin-bottom:8px}.lp-price-amount{font-size:40px;font-weight:900;color:var(--lp-primary);font-family:Outfit,sans-serif;line-height:1}.lp-price-period{font-size:16px;color:var(--lp-gray-600)}.lp-pricing-desc{font-size:13px;color:var(--lp-gray-600);margin:0}.lp-pricing-features{list-style:none;padding:0;margin:0 0 28px;display:flex;flex-direction:column;gap:10px;flex:1}.lp-pricing-features li{font-size:14px;color:var(--lp-gray-700);padding-left:20px;position:relative}.lp-pricing-features li:before{content:\"\\2713\";position:absolute;left:0;color:var(--lp-primary);font-weight:700}.lp-btn-block{width:100%;justify-content:center;text-align:center}.lp-final-cta{background:linear-gradient(135deg,var(--lp-primary-dark, #152C4A) 0%,var(--lp-primary) 100%);padding:96px 0}.lp-cta-content{text-align:center}.lp-cta-content h2{font-size:40px;font-weight:900;color:#fff;margin:0 0 16px;font-family:Outfit,sans-serif;letter-spacing:-.02em}.lp-cta-content p{font-size:18px;color:#ffffffbf;margin:0 auto 36px;max-width:560px}.lp-cta-buttons{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}.lp-btn-white{background:#fff;color:var(--lp-primary);border:2px solid white;font-weight:700}.lp-btn-white:hover{background:#ffffffe6;transform:translateY(-1px)}.lp-btn-outline-white{background:transparent;color:#fff;border:2px solid rgba(255,255,255,.5);font-weight:700}.lp-btn-outline-white:hover{background:#ffffff1a;border-color:#fff;transform:translateY(-1px)}.lp-footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:48px;padding:64px 0 48px}.lp-footer-brand-col{display:flex;flex-direction:column;gap:16px}.lp-footer-brand-custom{display:flex;align-items:center;gap:10px}.lp-footer-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-footer-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-footer-brand-wordmark{display:flex;flex-direction:column;line-height:1.1}.lp-footer-wordmark-line1{font-size:15px;font-weight:800;color:#fff;font-family:Outfit,sans-serif}.lp-footer-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-footer-brand-default{display:flex;align-items:center;gap:10px}.lp-footer-tagline{font-size:14px;color:#ffffff80;line-height:1.6;margin:0;max-width:280px}.lp-footer-col{display:flex;flex-direction:column;gap:12px}.lp-footer-col h4{font-size:13px;font-weight:700;color:#fff;margin:0 0 4px;letter-spacing:.04em;text-transform:uppercase;font-family:Outfit,sans-serif}.lp-footer-col a{font-size:14px;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-col a:hover{color:#fff}.lp-footer-bottom{border-top:1px solid rgba(255,255,255,.08);padding:24px 0;font-size:13px;color:#ffffff59}@media (max-width: 768px){.lp-hero-visual{max-width:100%;margin-top:32px}.lp-workflow-road{flex-direction:column;align-items:center;gap:32px}.lp-road-line{display:none}.lp-waypoint-desc{max-width:260px}.lp-security-grid,.lp-testimonials-grid,.lp-pricing-grid{grid-template-columns:1fr}.lp-pricing-popular{transform:none}.lp-pricing-popular:hover{transform:translateY(-3px)}.lp-cta-content h2{font-size:28px}.lp-footer-grid{grid-template-columns:1fr 1fr;gap:32px;padding:48px 0 32px}}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
|
|
14292
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SpaLandingComponent, isStandalone: false, selector: "spa-landing", inputs: { config: "config" }, host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0, template: "<!-- ===== NAVBAR ===== -->\r\n<nav class=\"lp-navbar\" [class.lp-navbar-scrolled]=\"navScrolled\">\r\n <div class=\"lp-container lp-nav-inner\">\r\n <!-- Brand: custom SVG override OR default img+name -->\r\n <div class=\"lp-nav-brand\" (click)=\"scrollToSection('lp-hero')\">\r\n <ng-container *ngIf=\"config.navBrand; else defaultNavBrand\">\r\n <span class=\"lp-nav-brand-svg\" [innerHTML]=\"safeNavBrandSvg\"></span>\r\n <div *ngIf=\"config.navBrand.wordmarkLine1\" class=\"lp-nav-brand-wordmark\">\r\n <span class=\"lp-nav-wordmark-line1\">{{ config.navBrand.wordmarkLine1 }}</span>\r\n <span *ngIf=\"config.navBrand.wordmarkLine2\" class=\"lp-nav-wordmark-line2\">{{ config.navBrand.wordmarkLine2 }}</span>\r\n </div>\r\n </ng-container>\r\n <ng-template #defaultNavBrand>\r\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-nav-logo\">\r\n <span class=\"lp-nav-brand-text\">{{ config.appName }}</span>\r\n </ng-template>\r\n </div>\r\n <!-- Desktop nav links -->\r\n <div class=\"lp-nav-links\">\r\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\r\n </div>\r\n <!-- Mobile menu overlay -->\r\n <div class=\"lp-mobile-menu\" [class.lp-mobile-menu-open]=\"mobileMenuOpen\">\r\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\r\n <div class=\"lp-mobile-auth\">\r\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\r\n <button class=\"lp-btn lp-btn-primary\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\r\n </div>\r\n </div>\r\n <!-- Desktop auth -->\r\n <div class=\"lp-nav-auth\">\r\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\r\n <button class=\"lp-btn lp-btn-primary lp-btn-sm\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\r\n </div>\r\n <!-- Hamburger for mobile -->\r\n <button class=\"lp-hamburger\" (click)=\"toggleMobileMenu()\" [class.lp-hamburger-open]=\"mobileMenuOpen\">\r\n <span></span><span></span><span></span>\r\n </button>\r\n </div>\r\n</nav>\r\n\r\n<!-- ===== HERO ===== -->\r\n<section id=\"lp-hero\" class=\"lp-hero-bg\" [class.lp-hero-split]=\"config.hero.layout === 'split'\">\r\n <!-- Optional faded watermark SVG (e.g. company logo) -->\r\n <!-- Changed: wrapper div gets Angular scoping attribute; inner div holds innerHTML so svg size CSS applies to scoped .lp-hero-watermark-inner -->\r\n <div *ngIf=\"config.hero.watermarkSvg\" class=\"lp-hero-watermark\" aria-hidden=\"true\">\r\n <div class=\"lp-hero-watermark-inner\" [innerHTML]=\"safeHeroWatermarkSvg\"></div>\r\n </div>\r\n\r\n <div class=\"lp-container lp-hero-content\">\r\n <!-- Left / centered column: brand, headline, CTAs -->\r\n <div class=\"lp-hero-main\">\r\n <!-- App brand: logo + name + decorative divider -->\r\n <div class=\"lp-hero-brand\">\r\n <div class=\"lp-hero-brand-logo\">\r\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-hero-brand-icon\">\r\n <span class=\"lp-hero-brand-name\">{{ config.appName }}</span>\r\n </div>\r\n <div class=\"lp-hero-divider\" aria-hidden=\"true\">\r\n <span class=\"lp-hero-divider-line\"></span>\r\n <span class=\"lp-hero-divider-dot\"></span>\r\n <span class=\"lp-hero-divider-line\"></span>\r\n </div>\r\n </div>\r\n <!-- Hero text: headline, subtitle, CTAs, trust signals -->\r\n <div class=\"lp-hero-text\">\r\n <div *ngIf=\"config.hero.badge\" class=\"lp-hero-badge\">{{ config.hero.badge }}</div>\r\n <h1>{{ config.hero.headline }} <span class=\"lp-gradient-text\">{{ config.hero.gradientText }}</span></h1>\r\n <p class=\"lp-hero-subtitle\">{{ config.hero.subtitle }}</p>\r\n <div class=\"lp-hero-ctas\">\r\n <button class=\"lp-btn lp-btn-primary lp-btn-lg\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">\r\n {{ config.hero.primaryCTA }}\r\n </button>\r\n <button class=\"lp-btn lp-btn-outline lp-btn-lg\" (click)=\"scrollToSection(config.hero.secondaryCTASection)\">\r\n {{ config.hero.secondaryCTA }}\r\n </button>\r\n </div>\r\n <!-- Trust signals with checkmark SVG icons -->\r\n <div class=\"lp-trust-signals\">\r\n <div *ngFor=\"let signal of config.hero.trustSignals\" class=\"lp-trust-item\">\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\r\n <path d=\"M20 6L9 17l-5-5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n <span>{{ signal }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- App visual: always rendered when projected content exists (stacked below hero text) -->\r\n <!-- Changed: removed *ngIf so visual always renders below text; layout is always stacked/column -->\r\n <div class=\"lp-hero-visual\">\r\n <ng-content select=\"[lpHeroVisual]\"></ng-content>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== METRICS (optional) ===== -->\r\n<section *ngIf=\"config.metrics\" class=\"lp-section lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-metrics-grid\">\r\n <div *ngFor=\"let metric of config.metrics\" class=\"lp-metric-card\">\r\n <div class=\"lp-metric-value\">{{ metric.value }}</div>\r\n <div class=\"lp-metric-label\">{{ metric.label }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== FEATURES ===== -->\r\n<section id=\"lp-features\" class=\"lp-section lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.features.title }}</h2>\r\n <p>{{ config.features.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-features-grid\">\r\n <div *ngFor=\"let feature of config.features.items\" class=\"lp-feature-card\">\r\n <!-- Icon tint: hex + '20' = ~12% opacity background -->\r\n <div class=\"lp-feature-icon\"\r\n [style.background]=\"iconBg(feature.color)\"\r\n [style.color]=\"feature.color\">\r\n <span class=\"material-icons\">{{ feature.icon }}</span>\r\n </div>\r\n <h3 class=\"lp-feature-title\">{{ feature.title }}</h3>\r\n <p class=\"lp-feature-desc\">{{ feature.description }}</p>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== WORKFLOW (optional) ===== -->\r\n<section *ngIf=\"config.workflow\" id=\"lp-workflow\" class=\"lp-section lp-section-alt lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.workflow!.title }}</h2>\r\n <p *ngIf=\"config.workflow!.subtitle\">{{ config.workflow!.subtitle }}</p>\r\n </div>\r\n\r\n <!-- Road metaphor layout: horizontal road line with waypoints -->\r\n <div *ngIf=\"config.workflow!.style === 'road'\" class=\"lp-workflow-road\">\r\n <div class=\"lp-road-line\"></div>\r\n <div *ngFor=\"let step of config.workflow!.steps\" class=\"lp-waypoint\">\r\n <div class=\"lp-waypoint-marker\">\r\n <span class=\"material-icons\">{{ step.icon }}</span>\r\n </div>\r\n <div class=\"lp-waypoint-number\">Step {{ step.number }}</div>\r\n <div class=\"lp-waypoint-title\">{{ step.title }}</div>\r\n <div class=\"lp-waypoint-desc\">{{ step.description }}</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default steps grid layout -->\r\n <div *ngIf=\"!config.workflow!.style || config.workflow!.style === 'steps'\" class=\"lp-steps-grid\">\r\n <div *ngFor=\"let step of config.workflow!.steps; let i = index\" class=\"lp-step-card\">\r\n <div class=\"lp-step-number\">{{ step.number }}</div>\r\n <div class=\"lp-step-icon\">\r\n <span class=\"material-icons\">{{ step.icon }}</span>\r\n </div>\r\n <h3 class=\"lp-step-title\">{{ step.title }}</h3>\r\n <p class=\"lp-step-desc\">{{ step.description }}</p>\r\n <!-- Connector line between steps, not after last -->\r\n <div *ngIf=\"i < config.workflow!.steps.length - 1\" class=\"lp-step-connector\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== CUSTOM SECTION SLOT ===== -->\r\n<!-- Consumer projects app-specific sections here (fleet board, benefits rows, etc.)\r\n using [lpCustomSection] attribute on a wrapper element -->\r\n<ng-content select=\"[lpCustomSection]\"></ng-content>\r\n\r\n<!-- ===== MODULES (optional) ===== -->\r\n<section *ngIf=\"config.modules\" id=\"lp-modules\" class=\"lp-section lp-section-alt lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.modules!.title }}</h2>\r\n <p>{{ config.modules!.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-modules-grid\">\r\n <div *ngFor=\"let group of config.modules!.groups\" class=\"lp-module-group\">\r\n <div class=\"lp-module-group-label\">{{ group.category }}</div>\r\n <div class=\"lp-module-tiles\">\r\n <div *ngFor=\"let tile of group.tiles\" class=\"lp-module-tile\">\r\n <span class=\"material-icons\">{{ tile.icon }}</span>\r\n <span>{{ tile.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== SECURITY (optional \u2014 dark background) ===== -->\r\n<section *ngIf=\"config.security\" class=\"lp-section lp-section-dark lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header lp-header-light\">\r\n <h2>{{ config.security!.title }}</h2>\r\n <p>{{ config.security!.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-security-grid\">\r\n <div *ngFor=\"let item of config.security!.items\" class=\"lp-security-card\">\r\n <div class=\"lp-security-icon\">\r\n <span class=\"material-icons\">{{ item.icon }}</span>\r\n </div>\r\n <h3>{{ item.title }}</h3>\r\n <p>{{ item.description }}</p>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== TESTIMONIALS (optional) ===== -->\r\n<section *ngIf=\"config.testimonials\" class=\"lp-section lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.testimonials!.title }}</h2>\r\n <p *ngIf=\"config.testimonials!.subtitle\">{{ config.testimonials!.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-testimonials-grid\">\r\n <div *ngFor=\"let item of config.testimonials!.items\" class=\"lp-testimonial-card\">\r\n <div class=\"lp-stars\">\r\n <span class=\"material-icons\">star</span>\r\n <span class=\"material-icons\">star</span>\r\n <span class=\"material-icons\">star</span>\r\n <span class=\"material-icons\">star</span>\r\n <span class=\"material-icons\">star</span>\r\n </div>\r\n <p class=\"lp-testimonial-quote\">\"{{ item.quote }}\"</p>\r\n <div class=\"lp-testimonial-author\">\r\n <div class=\"lp-author-avatar\">{{ item.authorInitials }}</div>\r\n <div>\r\n <div class=\"lp-author-name\">{{ item.authorName }}</div>\r\n <div class=\"lp-author-role\">{{ item.authorRole }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== PRICING (optional) ===== -->\r\n<section *ngIf=\"config.pricing\" id=\"lp-pricing\" class=\"lp-section lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.pricing!.title }}</h2>\r\n <p *ngIf=\"config.pricing!.subtitle\">{{ config.pricing!.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-pricing-grid\">\r\n <div *ngFor=\"let plan of config.pricing!.plans\"\r\n class=\"lp-pricing-card\"\r\n [class.lp-pricing-popular]=\"plan.popular\">\r\n <div *ngIf=\"plan.popular\" class=\"lp-popular-badge\">Most Popular</div>\r\n <div class=\"lp-pricing-header\">\r\n <h3>{{ plan.name }}</h3>\r\n <div class=\"lp-pricing-price\">\r\n <span class=\"lp-price-amount\">{{ plan.price }}</span>\r\n <span *ngIf=\"plan.period\" class=\"lp-price-period\">{{ plan.period }}</span>\r\n </div>\r\n <p class=\"lp-pricing-desc\">{{ plan.description }}</p>\r\n </div>\r\n <ul class=\"lp-pricing-features\">\r\n <li *ngFor=\"let feature of plan.features\">{{ feature }}</li>\r\n </ul>\r\n <!-- Popular plans get primary button, others get outline -->\r\n <button *ngIf=\"plan.popular\"\r\n class=\"lp-btn lp-btn-primary lp-btn-block\"\r\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\r\n {{ plan.ctaLabel }}\r\n </button>\r\n <button *ngIf=\"!plan.popular\"\r\n class=\"lp-btn lp-btn-outline lp-btn-block\"\r\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\r\n {{ plan.ctaLabel }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== FINAL CTA BANNER (optional) ===== -->\r\n<section *ngIf=\"config.finalCta\" class=\"lp-final-cta lp-animate\">\r\n <div class=\"lp-container lp-cta-content\">\r\n <h2>{{ config.finalCta!.headline }}</h2>\r\n <p>{{ config.finalCta!.subtitle }}</p>\r\n <div class=\"lp-cta-buttons\">\r\n <button class=\"lp-btn lp-btn-white lp-btn-lg\"\r\n (click)=\"navigateTo(config.finalCta!.primaryCTARoute || 'signup')\">\r\n {{ config.finalCta!.primaryCTA }}\r\n </button>\r\n <button *ngIf=\"config.finalCta!.secondaryCTA\"\r\n class=\"lp-btn lp-btn-outline-white lp-btn-lg\"\r\n (click)=\"scrollToSection(config.finalCta!.secondaryCTASection || '')\">\r\n {{ config.finalCta!.secondaryCTA }}\r\n </button>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== FOOTER ===== -->\r\n<footer class=\"lp-footer\" id=\"lp-footer\">\r\n <div class=\"lp-container\">\r\n\r\n <!-- Rich multi-column footer when footerColumns provided -->\r\n <div *ngIf=\"config.footerColumns && config.footerColumns.length; else simpleFooter\" class=\"lp-footer-grid\">\r\n <!-- Brand column -->\r\n <div class=\"lp-footer-brand-col\">\r\n <!-- Custom SVG footer brand OR default img+name -->\r\n <ng-container *ngIf=\"config.footerBrand; else defaultFooterBrand\">\r\n <div class=\"lp-footer-brand-custom\">\r\n <span class=\"lp-footer-brand-svg\" [innerHTML]=\"safeFooterBrandSvg\"></span>\r\n <div *ngIf=\"config.footerBrand.wordmarkLine1\" class=\"lp-footer-brand-wordmark\">\r\n <span class=\"lp-footer-wordmark-line1\">{{ config.footerBrand.wordmarkLine1 }}</span>\r\n <span *ngIf=\"config.footerBrand.wordmarkLine2\" class=\"lp-footer-wordmark-line2\">{{ config.footerBrand.wordmarkLine2 }}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #defaultFooterBrand>\r\n <div class=\"lp-footer-brand-default\">\r\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\r\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\r\n </div>\r\n </ng-template>\r\n <p *ngIf=\"config.footerTagline\" class=\"lp-footer-tagline\">{{ config.footerTagline }}</p>\r\n </div>\r\n <!-- Link columns -->\r\n <div *ngFor=\"let col of config.footerColumns\" class=\"lp-footer-col\">\r\n <h4>{{ col.title }}</h4>\r\n <a *ngFor=\"let link of col.links\"\r\n (click)=\"link.section ? scrollToSection(link.section) : (link.route ? navigateTo(link.route) : null)\">\r\n {{ link.label }}\r\n </a>\r\n </div>\r\n </div>\r\n\r\n <!-- Simple single-row footer (default) -->\r\n <ng-template #simpleFooter>\r\n <div class=\"lp-footer-inner\">\r\n <div class=\"lp-footer-brand\">\r\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\r\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\r\n </div>\r\n <div class=\"lp-footer-links\">\r\n <a (click)=\"navigateTo('login')\">Log in</a>\r\n <a (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Sign up</a>\r\n </div>\r\n <div class=\"lp-footer-copy\">\r\n © {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Footer bottom bar (shown for both layouts) -->\r\n <div *ngIf=\"config.footerColumns && config.footerColumns.length\" class=\"lp-footer-bottom\">\r\n <span>© {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.</span>\r\n </div>\r\n </div>\r\n</footer>\r\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap\";:host{--lp-primary: #1E3A5F;--lp-primary-dark: #152C4A;--lp-primary-light: #EBF0F7;--lp-secondary: #F59E0B;--lp-secondary-light:#FEF3C7;--lp-dark: #0D1117;--lp-gray-800: #1F2937;--lp-gray-700: #374151;--lp-gray-600: #4B5563;--lp-gray-400: #9CA3AF;--lp-gray-200: #E5E7EB;--lp-gray-100: #F3F4F6;--lp-gray-50: #F9FAFB;--lp-white: #FFFFFF;--lp-radius: 12px;--lp-radius-sm: 8px;--lp-radius-lg: 20px;--lp-radius-xl: 28px;--lp-shadow-xs: 0 1px 2px rgba(0,0,0,.05);--lp-shadow: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.05);--lp-shadow-md: 0 4px 8px rgba(0,0,0,.06), 0 2px 4px rgba(0,0,0,.04);--lp-shadow-lg: 0 12px 28px rgba(0,0,0,.1), 0 4px 8px rgba(0,0,0,.06);--lp-shadow-xl: 0 20px 40px rgba(0,0,0,.12), 0 8px 16px rgba(0,0,0,.07);--lp-ease: cubic-bezier(.4, 0, .2, 1);--lp-spring: cubic-bezier(.34, 1.56, .64, 1);display:block;font-family:Outfit,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--lp-gray-700);line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased}.lp-container{max-width:1200px;margin:0 auto;padding:0 32px}.lp-navbar{position:fixed;top:0;left:0;right:0;z-index:1000;padding:18px 0;transition:background .3s var(--lp-ease),padding .3s var(--lp-ease),box-shadow .3s var(--lp-ease)}.lp-navbar-scrolled{background:#ffffffeb;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);box-shadow:0 1px #0000000f,0 2px 8px #0000000a;padding:12px 0}.lp-nav-inner{display:flex;align-items:center;justify-content:space-between}.lp-nav-brand{display:flex;align-items:center;gap:10px;cursor:pointer;text-decoration:none}.lp-nav-logo{width:30px;height:30px;border-radius:7px;object-fit:contain}.lp-nav-brand-text{font-size:18px;font-weight:700;color:var(--lp-primary);letter-spacing:-.01em}.lp-nav-links{display:flex;align-items:center;gap:36px}.lp-nav-links a{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none;letter-spacing:.01em}.lp-nav-links a:hover{color:var(--lp-primary)}.lp-nav-auth{display:flex;align-items:center;gap:12px}.lp-nav-login{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none}.lp-nav-login:hover{color:var(--lp-primary)}.lp-hamburger{display:none;flex-direction:column;gap:5px;background:none;border:none;cursor:pointer;padding:4px;z-index:1001}.lp-hamburger span{display:block;width:22px;height:2px;background:var(--lp-gray-700);border-radius:2px;transition:transform .3s var(--lp-ease),opacity .3s}.lp-hamburger-open span:nth-child(1){transform:translateY(7px) rotate(45deg)}.lp-hamburger-open span:nth-child(2){opacity:0}.lp-hamburger-open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.lp-mobile-menu{display:none}.lp-mobile-auth{display:flex;flex-direction:column;gap:12px;margin-top:24px;padding-top:24px;border-top:1px solid var(--lp-gray-200)}.lp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:Outfit,sans-serif;font-size:14px;font-weight:600;padding:10px 22px;border-radius:var(--lp-radius-sm);border:none;cursor:pointer;transition:all .25s var(--lp-ease);text-decoration:none;white-space:nowrap;letter-spacing:.01em}.lp-btn-primary{background:var(--lp-primary);color:var(--lp-white);box-shadow:0 1px 3px #00000026,inset 0 1px #ffffff1f}.lp-btn-primary:hover{background:var(--lp-primary-dark);transform:translateY(-1px);box-shadow:0 6px 16px #0003,inset 0 1px #ffffff1f}.lp-btn-primary:active{transform:translateY(0)}.lp-btn-outline{background:transparent;color:var(--lp-primary);border:1.5px solid var(--lp-primary)}.lp-btn-outline:hover{background:var(--lp-primary);color:var(--lp-white);transform:translateY(-1px);box-shadow:0 4px 12px #00000026}.lp-btn-sm{padding:7px 16px;font-size:13px}.lp-btn-lg{padding:14px 32px;font-size:15px;border-radius:var(--lp-radius)}.lp-btn-block{width:100%}.lp-hero-bg{position:relative;overflow:hidden;padding:140px 0 100px;background:radial-gradient(ellipse 80% 60% at 60% -10%,color-mix(in srgb,var(--lp-primary) 8%,transparent) 0%,transparent 70%),radial-gradient(ellipse 60% 50% at -10% 80%,color-mix(in srgb,var(--lp-secondary) 6%,transparent) 0%,transparent 70%),linear-gradient(160deg,var(--lp-primary-light) 0%,#FAFBFF 45%,var(--lp-secondary-light) 100%);min-height:100vh;display:flex;align-items:center}.lp-hero-bg:before{content:\"\";position:absolute;inset:0;background-image:radial-gradient(circle,rgba(0,0,0,.04) 1px,transparent 1px);background-size:32px 32px;pointer-events:none;-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%);mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%)}.lp-hero-bg:after{content:\"\";position:absolute;width:700px;height:700px;border-radius:50%;background:color-mix(in srgb,var(--lp-primary) 5%,transparent);top:-200px;right:-150px;filter:blur(100px);pointer-events:none}.lp-hero-content{position:relative;z-index:1;text-align:center;width:100%}.lp-hero-badge{display:inline-flex;align-items:center;gap:6px;background:color-mix(in srgb,var(--lp-primary) 10%,transparent);color:var(--lp-primary);border:1px solid color-mix(in srgb,var(--lp-primary) 20%,transparent);border-radius:100px;padding:6px 16px;font-size:13px;font-weight:600;margin-bottom:20px;letter-spacing:.02em}.lp-hero-brand{margin-bottom:20px;text-align:center}.lp-hero-brand-logo{display:inline-flex;align-items:center;gap:14px;margin-bottom:12px}.lp-hero-brand-icon{height:52px;width:52px;border-radius:14px;object-fit:contain;box-shadow:0 4px 12px #0000001f}.lp-hero-brand-name{font-size:38px;font-weight:800;color:var(--lp-primary);letter-spacing:-.03em;font-family:Outfit,sans-serif}.lp-hero-divider{display:flex;align-items:center;justify-content:center;gap:10px}.lp-hero-divider-line{display:block;height:1px;width:40px;background:linear-gradient(90deg,transparent,var(--lp-primary),transparent);opacity:.3}.lp-hero-divider-dot{display:block;height:5px;width:5px;border-radius:50%;background:var(--lp-secondary);opacity:.8}.lp-hero-text{max-width:760px;margin:0 auto 64px}.lp-hero-text h1{font-family:Outfit,sans-serif;font-size:68px;font-weight:900;line-height:1.05;color:var(--lp-dark);margin:0 0 24px;letter-spacing:-.03em}.lp-gradient-text{background:linear-gradient(135deg,var(--lp-primary) 0%,var(--lp-secondary) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.lp-hero-subtitle{font-size:19px;color:var(--lp-gray-600);line-height:1.65;font-weight:400;max-width:560px;margin:0 auto 36px}.lp-hero-ctas{display:flex;align-items:center;justify-content:center;gap:14px;margin-bottom:32px;flex-wrap:wrap}.lp-trust-signals{display:flex;align-items:center;justify-content:center;gap:28px;flex-wrap:wrap}.lp-trust-item{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:500;color:var(--lp-gray-500, #6B7280)}.lp-trust-item svg{color:var(--lp-primary);flex-shrink:0}.lp-section{padding:100px 0;background:var(--lp-white)}.lp-section-alt{background:var(--lp-gray-50)}.lp-section-header{text-align:center;margin-bottom:64px}.lp-section-header h2{font-family:Outfit,sans-serif;font-size:42px;font-weight:800;color:var(--lp-dark);margin:0 0 14px;letter-spacing:-.025em;line-height:1.15}.lp-section-header p{font-size:17px;color:var(--lp-gray-600);max-width:540px;margin:0 auto;line-height:1.65}.lp-features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.lp-feature-card{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:36px 28px;transition:transform .25s var(--lp-ease),box-shadow .25s var(--lp-ease);border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);position:relative;overflow:hidden}.lp-feature-card:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--lp-primary),var(--lp-secondary));transform:scaleX(0);transform-origin:left;transition:transform .3s var(--lp-ease);border-radius:3px 3px 0 0}.lp-feature-card:hover{transform:translateY(-5px);box-shadow:var(--lp-shadow-xl);border-color:transparent}.lp-feature-card:hover:before{transform:scaleX(1)}.lp-feature-icon{width:52px;height:52px;border-radius:var(--lp-radius-sm);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.lp-feature-icon .material-icons{font-size:26px}.lp-feature-title{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-dark);margin:0 0 10px;letter-spacing:-.01em}.lp-feature-desc{font-size:14px;color:var(--lp-gray-600);line-height:1.65;margin:0}.lp-steps-grid{display:flex;align-items:flex-start;justify-content:space-between;position:relative;gap:16px}.lp-step-card{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;padding:0 8px}.lp-step-number{font-size:11px;font-weight:700;color:var(--lp-secondary);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px}.lp-step-icon{width:56px;height:56px;border-radius:50%;background:var(--lp-primary);display:flex;align-items:center;justify-content:center;margin-bottom:18px;box-shadow:0 4px 14px color-mix(in srgb,var(--lp-primary) 30%,transparent);border:3px solid var(--lp-white);position:relative;z-index:1}.lp-step-icon .material-icons{font-size:24px;color:var(--lp-white)}.lp-step-title{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--lp-dark);margin:0 0 8px;letter-spacing:-.01em}.lp-step-desc{font-size:13px;color:var(--lp-gray-600);line-height:1.55;margin:0}.lp-step-connector{position:absolute;top:40px;left:60%;width:80%;height:2px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 0px,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 10px,transparent 10px,transparent 18px);pointer-events:none;z-index:0}.lp-metrics-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:1px solid var(--lp-gray-200);border-radius:var(--lp-radius-lg);overflow:hidden;background:var(--lp-white);box-shadow:var(--lp-shadow)}.lp-metric-card{text-align:center;padding:40px 24px;border-right:1px solid var(--lp-gray-200);transition:background .2s}.lp-metric-card:last-child{border-right:none}.lp-metric-card:hover{background:var(--lp-gray-50)}.lp-metric-value{font-family:Outfit,sans-serif;font-size:48px;font-weight:900;color:var(--lp-primary);line-height:1;letter-spacing:-.03em;margin-bottom:8px}.lp-metric-label{font-size:14px;font-weight:500;color:var(--lp-gray-600)}.lp-modules-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px}.lp-module-group{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:28px 28px 24px;border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);transition:box-shadow .2s}.lp-module-group:hover{box-shadow:var(--lp-shadow-md)}.lp-module-group-label{font-family:Outfit,sans-serif;font-size:11px;font-weight:700;color:var(--lp-primary);margin-bottom:16px;text-transform:uppercase;letter-spacing:1px}.lp-module-tiles{display:flex;flex-wrap:wrap;gap:8px}.lp-module-tile{display:inline-flex;align-items:center;gap:6px;background:var(--lp-gray-50);border:1px solid var(--lp-gray-200);padding:7px 13px;border-radius:100px;font-size:12px;font-weight:600;color:var(--lp-gray-700);transition:all .2s var(--lp-ease);cursor:default}.lp-module-tile:hover{background:color-mix(in srgb,var(--lp-primary) 6%,transparent);border-color:color-mix(in srgb,var(--lp-primary) 25%,transparent);color:var(--lp-primary);transform:translateY(-1px)}.lp-module-tile .material-icons{font-size:15px;color:var(--lp-primary)}.lp-footer{background:var(--lp-dark);padding:44px 0 36px;border-top:1px solid rgba(255,255,255,.04)}.lp-footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:20px}.lp-footer-brand{display:flex;align-items:center;gap:10px}.lp-footer-logo{width:30px;height:30px;border-radius:7px;object-fit:contain;opacity:.9}.lp-footer-name{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-white);letter-spacing:-.01em}.lp-footer-links{display:flex;gap:28px}.lp-footer-links a{font-size:14px;font-weight:500;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-links a:hover{color:var(--lp-white)}.lp-footer-copy{font-size:13px;color:#ffffff59;width:100%;text-align:center;padding-top:20px;margin-top:4px;border-top:1px solid rgba(255,255,255,.06)}.lp-animate{opacity:0;transform:translateY(28px);transition:opacity .65s var(--lp-ease),transform .65s var(--lp-ease)}.lp-visible{opacity:1;transform:translateY(0)}.lp-visible .lp-feature-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-feature-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-feature-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-feature-card:nth-child(4){transition-delay:.18s}.lp-visible .lp-feature-card:nth-child(5){transition-delay:.24s}.lp-visible .lp-feature-card:nth-child(6){transition-delay:.3s}.lp-visible .lp-step-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-step-card:nth-child(2){transition-delay:80ms}.lp-visible .lp-step-card:nth-child(3){transition-delay:.16s}.lp-visible .lp-step-card:nth-child(4){transition-delay:.24s}.lp-visible .lp-metric-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-metric-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-metric-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-metric-card:nth-child(4){transition-delay:.18s}@media (max-width: 1024px){.lp-hero-text h1{font-size:52px}.lp-section-header h2{font-size:36px}.lp-features-grid,.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card:nth-child(2){border-right:none}.lp-metrics-grid .lp-metric-card:nth-child(3){border-top:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:nth-child(4){border-top:1px solid var(--lp-gray-200)}.lp-steps-grid{flex-wrap:wrap;justify-content:center;gap:40px}.lp-step-connector{display:none}}@media (max-width: 767px){.lp-container{padding:0 20px}.lp-nav-links{display:none}.lp-hamburger{display:flex}.lp-mobile-auth{display:none}.lp-mobile-menu{display:none;position:fixed;inset:0;background:#fffffff7;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;align-items:center;justify-content:center;gap:28px;z-index:999}.lp-mobile-menu-open{display:flex}.lp-mobile-menu a{font-size:22px;font-weight:700;color:var(--lp-dark);cursor:pointer;text-decoration:none;letter-spacing:-.01em}.lp-hero-bg{padding:100px 0 60px;min-height:auto}.lp-hero-text h1{font-size:38px}.lp-hero-brand-name{font-size:28px}.lp-hero-subtitle{font-size:16px}.lp-hero-ctas{flex-direction:column}.lp-hero-ctas .lp-btn{width:100%;justify-content:center}.lp-trust-signals{gap:16px}.lp-section{padding:72px 0}.lp-section-header{margin-bottom:40px}.lp-section-header h2{font-size:28px}.lp-features-grid{grid-template-columns:1fr}.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card{border-right:none;border-bottom:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:last-child{border-bottom:none}.lp-metric-value{font-size:36px}.lp-modules-grid{grid-template-columns:1fr}.lp-steps-grid{flex-direction:column;align-items:center;gap:32px}.lp-step-connector{display:none}.lp-footer-inner{flex-direction:column;align-items:center;text-align:center}}.lp-nav-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-nav-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-nav-brand-wordmark{display:flex;flex-direction:column;line-height:1.1;margin-left:8px}.lp-nav-wordmark-line1{font-size:15px;font-weight:800;color:#1a3a7a;letter-spacing:-.01em;font-family:Outfit,sans-serif}.lp-nav-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-hero-bg{position:relative}.lp-hero-watermark{position:absolute;inset:0;display:flex;align-items:flex-start;justify-content:center;padding-top:40px;pointer-events:none;z-index:1}.lp-hero-watermark-inner{width:700px;height:700px;opacity:.04;overflow:hidden;flex-shrink:0}@media (min-width: 768px){.lp-hero-watermark-inner{width:800px;height:800px}}.lp-hero-content{display:flex;flex-direction:column;align-items:center}.lp-hero-main{z-index:1;width:100%}.lp-hero-visual{position:relative;z-index:1;width:100%;max-width:900px;margin:48px auto 0}.lp-workflow-road{position:relative;display:flex;justify-content:space-between;align-items:flex-start;padding:48px 0 24px;gap:16px}.lp-road-line{position:absolute;top:72px;left:6%;right:6%;height:3px;background:linear-gradient(90deg,var(--lp-primary-light),var(--lp-primary),var(--lp-primary-light));border-radius:2px;opacity:.4}.lp-waypoint{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;z-index:1}.lp-waypoint-marker{width:52px;height:52px;border-radius:50%;background:var(--lp-primary);color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:12px;box-shadow:0 4px 16px color-mix(in srgb,var(--lp-primary) 35%,transparent);transition:transform .25s cubic-bezier(.34,1.56,.64,1)}.lp-waypoint:hover .lp-waypoint-marker{transform:translateY(-4px) scale(1.08)}.lp-waypoint-marker .material-icons{font-size:22px}.lp-waypoint-number{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--lp-primary);margin-bottom:6px;opacity:.7}.lp-waypoint-title{font-size:14px;font-weight:700;color:var(--lp-dark);margin-bottom:6px;font-family:Outfit,sans-serif}.lp-waypoint-desc{font-size:12px;color:var(--lp-gray-600);line-height:1.5;max-width:140px}.lp-section-dark{background:var(--lp-dark);color:#fff}.lp-section-dark.lp-section{padding:96px 0}.lp-header-light h2{color:#fff}.lp-header-light p{color:#ffffffa6}.lp-security-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}.lp-security-card{background:#ffffff0d;border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:32px 28px;transition:background .25s ease}.lp-security-card:hover{background:#ffffff14}.lp-security-icon{width:52px;height:52px;border-radius:14px;background:#ffffff1a;display:flex;align-items:center;justify-content:center;margin-bottom:20px;color:#fff}.lp-security-icon .material-icons{font-size:24px}.lp-security-card h3{font-size:18px;font-weight:700;color:#fff;margin:0 0 12px;font-family:Outfit,sans-serif}.lp-security-card p{font-size:14px;color:#fff9;line-height:1.65;margin:0}.lp-testimonials-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px}.lp-testimonial-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:32px 28px;display:flex;flex-direction:column;gap:16px;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-testimonial-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-2px)}.lp-stars{display:flex;gap:2px;color:var(--lp-secondary)}.lp-stars .material-icons{font-size:18px}.lp-testimonial-quote{font-size:15px;color:var(--lp-gray-700);line-height:1.65;font-style:italic;margin:0;flex:1}.lp-testimonial-author{display:flex;align-items:center;gap:12px}.lp-author-avatar{width:44px;height:44px;border-radius:50%;background:var(--lp-primary);color:#fff;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:Outfit,sans-serif}.lp-author-name{font-size:14px;font-weight:700;color:var(--lp-dark)}.lp-author-role{font-size:12px;color:var(--lp-gray-400)}.lp-pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;align-items:start}.lp-pricing-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:36px 28px 28px;position:relative;display:flex;flex-direction:column;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-pricing-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-3px)}.lp-pricing-popular{border-color:var(--lp-primary);box-shadow:0 0 0 2px var(--lp-primary);transform:scale(1.03)}.lp-pricing-popular:hover{transform:scale(1.03) translateY(-3px)}.lp-popular-badge{position:absolute;top:-12px;left:50%;transform:translate(-50%);background:var(--lp-primary);color:#fff;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:4px 14px;border-radius:100px;white-space:nowrap;font-family:Outfit,sans-serif}.lp-pricing-header{margin-bottom:24px}.lp-pricing-header h3{font-size:20px;font-weight:800;color:var(--lp-dark);margin:0 0 12px;font-family:Outfit,sans-serif}.lp-pricing-price{display:flex;align-items:baseline;gap:4px;margin-bottom:8px}.lp-price-amount{font-size:40px;font-weight:900;color:var(--lp-primary);font-family:Outfit,sans-serif;line-height:1}.lp-price-period{font-size:16px;color:var(--lp-gray-600)}.lp-pricing-desc{font-size:13px;color:var(--lp-gray-600);margin:0}.lp-pricing-features{list-style:none;padding:0;margin:0 0 28px;display:flex;flex-direction:column;gap:10px;flex:1}.lp-pricing-features li{font-size:14px;color:var(--lp-gray-700);padding-left:20px;position:relative}.lp-pricing-features li:before{content:\"\\2713\";position:absolute;left:0;color:var(--lp-primary);font-weight:700}.lp-btn-block{width:100%;justify-content:center;text-align:center}.lp-final-cta{background:linear-gradient(135deg,var(--lp-primary-dark, #152C4A) 0%,var(--lp-primary) 100%);padding:96px 0}.lp-cta-content{text-align:center}.lp-cta-content h2{font-size:40px;font-weight:900;color:#fff;margin:0 0 16px;font-family:Outfit,sans-serif;letter-spacing:-.02em}.lp-cta-content p{font-size:18px;color:#ffffffbf;margin:0 auto 36px;max-width:560px}.lp-cta-buttons{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}.lp-btn-white{background:#fff;color:var(--lp-primary);border:2px solid white;font-weight:700}.lp-btn-white:hover{background:#ffffffe6;transform:translateY(-1px)}.lp-btn-outline-white{background:transparent;color:#fff;border:2px solid rgba(255,255,255,.5);font-weight:700}.lp-btn-outline-white:hover{background:#ffffff1a;border-color:#fff;transform:translateY(-1px)}.lp-footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:48px;padding:64px 0 48px}.lp-footer-brand-col{display:flex;flex-direction:column;gap:16px}.lp-footer-brand-custom{display:flex;align-items:center;gap:10px}.lp-footer-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-footer-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-footer-brand-wordmark{display:flex;flex-direction:column;line-height:1.1}.lp-footer-wordmark-line1{font-size:15px;font-weight:800;color:#fff;font-family:Outfit,sans-serif}.lp-footer-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-footer-brand-default{display:flex;align-items:center;gap:10px}.lp-footer-tagline{font-size:14px;color:#ffffff80;line-height:1.6;margin:0;max-width:280px}.lp-footer-col{display:flex;flex-direction:column;gap:12px}.lp-footer-col h4{font-size:13px;font-weight:700;color:#fff;margin:0 0 4px;letter-spacing:.04em;text-transform:uppercase;font-family:Outfit,sans-serif}.lp-footer-col a{font-size:14px;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-col a:hover{color:#fff}.lp-footer-bottom{border-top:1px solid rgba(255,255,255,.08);padding:24px 0;font-size:13px;color:#ffffff59}@media (max-width: 768px){.lp-hero-visual{max-width:100%;margin-top:32px}.lp-workflow-road{flex-direction:column;align-items:center;gap:32px}.lp-road-line{display:none}.lp-waypoint-desc{max-width:260px}.lp-security-grid,.lp-testimonials-grid,.lp-pricing-grid{grid-template-columns:1fr}.lp-pricing-popular{transform:none}.lp-pricing-popular:hover{transform:translateY(-3px)}.lp-cta-content h2{font-size:28px}.lp-footer-grid{grid-template-columns:1fr 1fr;gap:32px;padding:48px 0 32px}}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
|
|
14187
14293
|
}
|
|
14188
14294
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SpaLandingComponent, decorators: [{
|
|
14189
14295
|
type: Component,
|
|
14190
|
-
args: [{ selector: 'spa-landing', standalone: false, template: "<!-- ===== NAVBAR ===== -->\n<nav class=\"lp-navbar\" [class.lp-navbar-scrolled]=\"navScrolled\">\n <div class=\"lp-container lp-nav-inner\">\n <!-- Brand: custom SVG override OR default img+name -->\n <div class=\"lp-nav-brand\" (click)=\"scrollToSection('lp-hero')\">\n <ng-container *ngIf=\"config.navBrand; else defaultNavBrand\">\n <span class=\"lp-nav-brand-svg\" [innerHTML]=\"safeNavBrandSvg\"></span>\n <div *ngIf=\"config.navBrand.wordmarkLine1\" class=\"lp-nav-brand-wordmark\">\n <span class=\"lp-nav-wordmark-line1\">{{ config.navBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.navBrand.wordmarkLine2\" class=\"lp-nav-wordmark-line2\">{{ config.navBrand.wordmarkLine2 }}</span>\n </div>\n </ng-container>\n <ng-template #defaultNavBrand>\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-nav-logo\">\n <span class=\"lp-nav-brand-text\">{{ config.appName }}</span>\n </ng-template>\n </div>\n <!-- Desktop nav links -->\n <div class=\"lp-nav-links\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n </div>\n <!-- Mobile menu overlay -->\n <div class=\"lp-mobile-menu\" [class.lp-mobile-menu-open]=\"mobileMenuOpen\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n <div class=\"lp-mobile-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n </div>\n <!-- Desktop auth -->\n <div class=\"lp-nav-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary lp-btn-sm\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n <!-- Hamburger for mobile -->\n <button class=\"lp-hamburger\" (click)=\"toggleMobileMenu()\" [class.lp-hamburger-open]=\"mobileMenuOpen\">\n <span></span><span></span><span></span>\n </button>\n </div>\n</nav>\n\n<!-- ===== HERO ===== -->\n<section id=\"lp-hero\" class=\"lp-hero-bg\" [class.lp-hero-split]=\"config.hero.layout === 'split'\">\n <!-- Optional faded watermark SVG (e.g. company logo) -->\n <!-- Changed: wrapper div gets Angular scoping attribute; inner div holds innerHTML so svg size CSS applies to scoped .lp-hero-watermark-inner -->\n <div *ngIf=\"config.hero.watermarkSvg\" class=\"lp-hero-watermark\" aria-hidden=\"true\">\n <div class=\"lp-hero-watermark-inner\" [innerHTML]=\"safeHeroWatermarkSvg\"></div>\n </div>\n\n <div class=\"lp-container lp-hero-content\">\n <!-- Left / centered column: brand, headline, CTAs -->\n <div class=\"lp-hero-main\">\n <!-- App brand: logo + name + decorative divider -->\n <div class=\"lp-hero-brand\">\n <div class=\"lp-hero-brand-logo\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-hero-brand-icon\">\n <span class=\"lp-hero-brand-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-hero-divider\" aria-hidden=\"true\">\n <span class=\"lp-hero-divider-line\"></span>\n <span class=\"lp-hero-divider-dot\"></span>\n <span class=\"lp-hero-divider-line\"></span>\n </div>\n </div>\n <!-- Hero text: headline, subtitle, CTAs, trust signals -->\n <div class=\"lp-hero-text\">\n <div *ngIf=\"config.hero.badge\" class=\"lp-hero-badge\">{{ config.hero.badge }}</div>\n <h1>{{ config.hero.headline }} <span class=\"lp-gradient-text\">{{ config.hero.gradientText }}</span></h1>\n <p class=\"lp-hero-subtitle\">{{ config.hero.subtitle }}</p>\n <div class=\"lp-hero-ctas\">\n <button class=\"lp-btn lp-btn-primary lp-btn-lg\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">\n {{ config.hero.primaryCTA }}\n </button>\n <button class=\"lp-btn lp-btn-outline lp-btn-lg\" (click)=\"scrollToSection(config.hero.secondaryCTASection)\">\n {{ config.hero.secondaryCTA }}\n </button>\n </div>\n <!-- Trust signals with checkmark SVG icons -->\n <div class=\"lp-trust-signals\">\n <div *ngFor=\"let signal of config.hero.trustSignals\" class=\"lp-trust-item\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M20 6L9 17l-5-5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n <span>{{ signal }}</span>\n </div>\n </div>\n </div>\n </div>\n <!-- App visual: always rendered when projected content exists (stacked below hero text) -->\n <!-- Changed: removed *ngIf so visual always renders below text; layout is always stacked/column -->\n <div class=\"lp-hero-visual\">\n <ng-content select=\"[lpHeroVisual]\"></ng-content>\n </div>\n </div>\n</section>\n\n<!-- ===== METRICS (optional) ===== -->\n<section *ngIf=\"config.metrics\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-metrics-grid\">\n <div *ngFor=\"let metric of config.metrics\" class=\"lp-metric-card\">\n <div class=\"lp-metric-value\">{{ metric.value }}</div>\n <div class=\"lp-metric-label\">{{ metric.label }}</div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FEATURES ===== -->\n<section id=\"lp-features\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.features.title }}</h2>\n <p>{{ config.features.subtitle }}</p>\n </div>\n <div class=\"lp-features-grid\">\n <div *ngFor=\"let feature of config.features.items\" class=\"lp-feature-card\">\n <!-- Icon tint: hex + '20' = ~12% opacity background -->\n <div class=\"lp-feature-icon\"\n [style.background]=\"iconBg(feature.color)\"\n [style.color]=\"feature.color\">\n <span class=\"material-icons\">{{ feature.icon }}</span>\n </div>\n <h3 class=\"lp-feature-title\">{{ feature.title }}</h3>\n <p class=\"lp-feature-desc\">{{ feature.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== WORKFLOW (optional) ===== -->\n<section *ngIf=\"config.workflow\" id=\"lp-workflow\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.workflow!.title }}</h2>\n <p *ngIf=\"config.workflow!.subtitle\">{{ config.workflow!.subtitle }}</p>\n </div>\n\n <!-- Road metaphor layout: horizontal road line with waypoints -->\n <div *ngIf=\"config.workflow!.style === 'road'\" class=\"lp-workflow-road\">\n <div class=\"lp-road-line\"></div>\n <div *ngFor=\"let step of config.workflow!.steps\" class=\"lp-waypoint\">\n <div class=\"lp-waypoint-marker\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <div class=\"lp-waypoint-number\">Step {{ step.number }}</div>\n <div class=\"lp-waypoint-title\">{{ step.title }}</div>\n <div class=\"lp-waypoint-desc\">{{ step.description }}</div>\n </div>\n </div>\n\n <!-- Default steps grid layout -->\n <div *ngIf=\"!config.workflow!.style || config.workflow!.style === 'steps'\" class=\"lp-steps-grid\">\n <div *ngFor=\"let step of config.workflow!.steps; let i = index\" class=\"lp-step-card\">\n <div class=\"lp-step-number\">{{ step.number }}</div>\n <div class=\"lp-step-icon\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <h3 class=\"lp-step-title\">{{ step.title }}</h3>\n <p class=\"lp-step-desc\">{{ step.description }}</p>\n <!-- Connector line between steps, not after last -->\n <div *ngIf=\"i < config.workflow!.steps.length - 1\" class=\"lp-step-connector\"></div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== CUSTOM SECTION SLOT ===== -->\n<!-- Consumer projects app-specific sections here (fleet board, benefits rows, etc.)\n using [lpCustomSection] attribute on a wrapper element -->\n<ng-content select=\"[lpCustomSection]\"></ng-content>\n\n<!-- ===== MODULES (optional) ===== -->\n<section *ngIf=\"config.modules\" id=\"lp-modules\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.modules!.title }}</h2>\n <p>{{ config.modules!.subtitle }}</p>\n </div>\n <div class=\"lp-modules-grid\">\n <div *ngFor=\"let group of config.modules!.groups\" class=\"lp-module-group\">\n <div class=\"lp-module-group-label\">{{ group.category }}</div>\n <div class=\"lp-module-tiles\">\n <div *ngFor=\"let tile of group.tiles\" class=\"lp-module-tile\">\n <span class=\"material-icons\">{{ tile.icon }}</span>\n <span>{{ tile.name }}</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== SECURITY (optional \u2014 dark background) ===== -->\n<section *ngIf=\"config.security\" class=\"lp-section lp-section-dark lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header lp-header-light\">\n <h2>{{ config.security!.title }}</h2>\n <p>{{ config.security!.subtitle }}</p>\n </div>\n <div class=\"lp-security-grid\">\n <div *ngFor=\"let item of config.security!.items\" class=\"lp-security-card\">\n <div class=\"lp-security-icon\">\n <span class=\"material-icons\">{{ item.icon }}</span>\n </div>\n <h3>{{ item.title }}</h3>\n <p>{{ item.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== TESTIMONIALS (optional) ===== -->\n<section *ngIf=\"config.testimonials\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.testimonials!.title }}</h2>\n <p *ngIf=\"config.testimonials!.subtitle\">{{ config.testimonials!.subtitle }}</p>\n </div>\n <div class=\"lp-testimonials-grid\">\n <div *ngFor=\"let item of config.testimonials!.items\" class=\"lp-testimonial-card\">\n <div class=\"lp-stars\">\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n </div>\n <p class=\"lp-testimonial-quote\">\"{{ item.quote }}\"</p>\n <div class=\"lp-testimonial-author\">\n <div class=\"lp-author-avatar\">{{ item.authorInitials }}</div>\n <div>\n <div class=\"lp-author-name\">{{ item.authorName }}</div>\n <div class=\"lp-author-role\">{{ item.authorRole }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== PRICING (optional) ===== -->\n<section *ngIf=\"config.pricing\" id=\"lp-pricing\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.pricing!.title }}</h2>\n <p *ngIf=\"config.pricing!.subtitle\">{{ config.pricing!.subtitle }}</p>\n </div>\n <div class=\"lp-pricing-grid\">\n <div *ngFor=\"let plan of config.pricing!.plans\"\n class=\"lp-pricing-card\"\n [class.lp-pricing-popular]=\"plan.popular\">\n <div *ngIf=\"plan.popular\" class=\"lp-popular-badge\">Most Popular</div>\n <div class=\"lp-pricing-header\">\n <h3>{{ plan.name }}</h3>\n <div class=\"lp-pricing-price\">\n <span class=\"lp-price-amount\">{{ plan.price }}</span>\n <span *ngIf=\"plan.period\" class=\"lp-price-period\">{{ plan.period }}</span>\n </div>\n <p class=\"lp-pricing-desc\">{{ plan.description }}</p>\n </div>\n <ul class=\"lp-pricing-features\">\n <li *ngFor=\"let feature of plan.features\">{{ feature }}</li>\n </ul>\n <!-- Popular plans get primary button, others get outline -->\n <button *ngIf=\"plan.popular\"\n class=\"lp-btn lp-btn-primary lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n <button *ngIf=\"!plan.popular\"\n class=\"lp-btn lp-btn-outline lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FINAL CTA BANNER (optional) ===== -->\n<section *ngIf=\"config.finalCta\" class=\"lp-final-cta lp-animate\">\n <div class=\"lp-container lp-cta-content\">\n <h2>{{ config.finalCta!.headline }}</h2>\n <p>{{ config.finalCta!.subtitle }}</p>\n <div class=\"lp-cta-buttons\">\n <button class=\"lp-btn lp-btn-white lp-btn-lg\"\n (click)=\"navigateTo(config.finalCta!.primaryCTARoute || 'signup')\">\n {{ config.finalCta!.primaryCTA }}\n </button>\n <button *ngIf=\"config.finalCta!.secondaryCTA\"\n class=\"lp-btn lp-btn-outline-white lp-btn-lg\"\n (click)=\"scrollToSection(config.finalCta!.secondaryCTASection || '')\">\n {{ config.finalCta!.secondaryCTA }}\n </button>\n </div>\n </div>\n</section>\n\n<!-- ===== FOOTER ===== -->\n<footer class=\"lp-footer\" id=\"lp-footer\">\n <div class=\"lp-container\">\n\n <!-- Rich multi-column footer when footerColumns provided -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length; else simpleFooter\" class=\"lp-footer-grid\">\n <!-- Brand column -->\n <div class=\"lp-footer-brand-col\">\n <!-- Custom SVG footer brand OR default img+name -->\n <ng-container *ngIf=\"config.footerBrand; else defaultFooterBrand\">\n <div class=\"lp-footer-brand-custom\">\n <span class=\"lp-footer-brand-svg\" [innerHTML]=\"safeFooterBrandSvg\"></span>\n <div *ngIf=\"config.footerBrand.wordmarkLine1\" class=\"lp-footer-brand-wordmark\">\n <span class=\"lp-footer-wordmark-line1\">{{ config.footerBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.footerBrand.wordmarkLine2\" class=\"lp-footer-wordmark-line2\">{{ config.footerBrand.wordmarkLine2 }}</span>\n </div>\n </div>\n </ng-container>\n <ng-template #defaultFooterBrand>\n <div class=\"lp-footer-brand-default\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n </ng-template>\n <p *ngIf=\"config.footerTagline\" class=\"lp-footer-tagline\">{{ config.footerTagline }}</p>\n </div>\n <!-- Link columns -->\n <div *ngFor=\"let col of config.footerColumns\" class=\"lp-footer-col\">\n <h4>{{ col.title }}</h4>\n <a *ngFor=\"let link of col.links\"\n (click)=\"link.section ? scrollToSection(link.section) : (link.route ? navigateTo(link.route) : null)\">\n {{ link.label }}\n </a>\n </div>\n </div>\n\n <!-- Simple single-row footer (default) -->\n <ng-template #simpleFooter>\n <div class=\"lp-footer-inner\">\n <div class=\"lp-footer-brand\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-footer-links\">\n <a (click)=\"navigateTo('login')\">Log in</a>\n <a (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Sign up</a>\n </div>\n <div class=\"lp-footer-copy\">\n © {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.\n </div>\n </div>\n </ng-template>\n\n <!-- Footer bottom bar (shown for both layouts) -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length\" class=\"lp-footer-bottom\">\n <span>© {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.</span>\n </div>\n </div>\n</footer>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap\";:host{--lp-primary: #1E3A5F;--lp-primary-dark: #152C4A;--lp-primary-light: #EBF0F7;--lp-secondary: #F59E0B;--lp-secondary-light:#FEF3C7;--lp-dark: #0D1117;--lp-gray-800: #1F2937;--lp-gray-700: #374151;--lp-gray-600: #4B5563;--lp-gray-400: #9CA3AF;--lp-gray-200: #E5E7EB;--lp-gray-100: #F3F4F6;--lp-gray-50: #F9FAFB;--lp-white: #FFFFFF;--lp-radius: 12px;--lp-radius-sm: 8px;--lp-radius-lg: 20px;--lp-radius-xl: 28px;--lp-shadow-xs: 0 1px 2px rgba(0,0,0,.05);--lp-shadow: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.05);--lp-shadow-md: 0 4px 8px rgba(0,0,0,.06), 0 2px 4px rgba(0,0,0,.04);--lp-shadow-lg: 0 12px 28px rgba(0,0,0,.1), 0 4px 8px rgba(0,0,0,.06);--lp-shadow-xl: 0 20px 40px rgba(0,0,0,.12), 0 8px 16px rgba(0,0,0,.07);--lp-ease: cubic-bezier(.4, 0, .2, 1);--lp-spring: cubic-bezier(.34, 1.56, .64, 1);display:block;font-family:Outfit,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--lp-gray-700);line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased}.lp-container{max-width:1200px;margin:0 auto;padding:0 32px}.lp-navbar{position:fixed;top:0;left:0;right:0;z-index:1000;padding:18px 0;transition:background .3s var(--lp-ease),padding .3s var(--lp-ease),box-shadow .3s var(--lp-ease)}.lp-navbar-scrolled{background:#ffffffeb;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);box-shadow:0 1px #0000000f,0 2px 8px #0000000a;padding:12px 0}.lp-nav-inner{display:flex;align-items:center;justify-content:space-between}.lp-nav-brand{display:flex;align-items:center;gap:10px;cursor:pointer;text-decoration:none}.lp-nav-logo{width:30px;height:30px;border-radius:7px;object-fit:contain}.lp-nav-brand-text{font-size:18px;font-weight:700;color:var(--lp-primary);letter-spacing:-.01em}.lp-nav-links{display:flex;align-items:center;gap:36px}.lp-nav-links a{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none;letter-spacing:.01em}.lp-nav-links a:hover{color:var(--lp-primary)}.lp-nav-auth{display:flex;align-items:center;gap:12px}.lp-nav-login{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none}.lp-nav-login:hover{color:var(--lp-primary)}.lp-hamburger{display:none;flex-direction:column;gap:5px;background:none;border:none;cursor:pointer;padding:4px;z-index:1001}.lp-hamburger span{display:block;width:22px;height:2px;background:var(--lp-gray-700);border-radius:2px;transition:transform .3s var(--lp-ease),opacity .3s}.lp-hamburger-open span:nth-child(1){transform:translateY(7px) rotate(45deg)}.lp-hamburger-open span:nth-child(2){opacity:0}.lp-hamburger-open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.lp-mobile-menu{display:none}.lp-mobile-auth{display:flex;flex-direction:column;gap:12px;margin-top:24px;padding-top:24px;border-top:1px solid var(--lp-gray-200)}.lp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:Outfit,sans-serif;font-size:14px;font-weight:600;padding:10px 22px;border-radius:var(--lp-radius-sm);border:none;cursor:pointer;transition:all .25s var(--lp-ease);text-decoration:none;white-space:nowrap;letter-spacing:.01em}.lp-btn-primary{background:var(--lp-primary);color:var(--lp-white);box-shadow:0 1px 3px #00000026,inset 0 1px #ffffff1f}.lp-btn-primary:hover{background:var(--lp-primary-dark);transform:translateY(-1px);box-shadow:0 6px 16px #0003,inset 0 1px #ffffff1f}.lp-btn-primary:active{transform:translateY(0)}.lp-btn-outline{background:transparent;color:var(--lp-primary);border:1.5px solid var(--lp-primary)}.lp-btn-outline:hover{background:var(--lp-primary);color:var(--lp-white);transform:translateY(-1px);box-shadow:0 4px 12px #00000026}.lp-btn-sm{padding:7px 16px;font-size:13px}.lp-btn-lg{padding:14px 32px;font-size:15px;border-radius:var(--lp-radius)}.lp-btn-block{width:100%}.lp-hero-bg{position:relative;overflow:hidden;padding:140px 0 100px;background:radial-gradient(ellipse 80% 60% at 60% -10%,color-mix(in srgb,var(--lp-primary) 8%,transparent) 0%,transparent 70%),radial-gradient(ellipse 60% 50% at -10% 80%,color-mix(in srgb,var(--lp-secondary) 6%,transparent) 0%,transparent 70%),linear-gradient(160deg,var(--lp-primary-light) 0%,#FAFBFF 45%,var(--lp-secondary-light) 100%);min-height:100vh;display:flex;align-items:center}.lp-hero-bg:before{content:\"\";position:absolute;inset:0;background-image:radial-gradient(circle,rgba(0,0,0,.04) 1px,transparent 1px);background-size:32px 32px;pointer-events:none;-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%);mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%)}.lp-hero-bg:after{content:\"\";position:absolute;width:700px;height:700px;border-radius:50%;background:color-mix(in srgb,var(--lp-primary) 5%,transparent);top:-200px;right:-150px;filter:blur(100px);pointer-events:none}.lp-hero-content{position:relative;z-index:1;text-align:center;width:100%}.lp-hero-badge{display:inline-flex;align-items:center;gap:6px;background:color-mix(in srgb,var(--lp-primary) 10%,transparent);color:var(--lp-primary);border:1px solid color-mix(in srgb,var(--lp-primary) 20%,transparent);border-radius:100px;padding:6px 16px;font-size:13px;font-weight:600;margin-bottom:20px;letter-spacing:.02em}.lp-hero-brand{margin-bottom:20px;text-align:center}.lp-hero-brand-logo{display:inline-flex;align-items:center;gap:14px;margin-bottom:12px}.lp-hero-brand-icon{height:52px;width:52px;border-radius:14px;object-fit:contain;box-shadow:0 4px 12px #0000001f}.lp-hero-brand-name{font-size:38px;font-weight:800;color:var(--lp-primary);letter-spacing:-.03em;font-family:Outfit,sans-serif}.lp-hero-divider{display:flex;align-items:center;justify-content:center;gap:10px}.lp-hero-divider-line{display:block;height:1px;width:40px;background:linear-gradient(90deg,transparent,var(--lp-primary),transparent);opacity:.3}.lp-hero-divider-dot{display:block;height:5px;width:5px;border-radius:50%;background:var(--lp-secondary);opacity:.8}.lp-hero-text{max-width:760px;margin:0 auto 64px}.lp-hero-text h1{font-family:Outfit,sans-serif;font-size:68px;font-weight:900;line-height:1.05;color:var(--lp-dark);margin:0 0 24px;letter-spacing:-.03em}.lp-gradient-text{background:linear-gradient(135deg,var(--lp-primary) 0%,var(--lp-secondary) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.lp-hero-subtitle{font-size:19px;color:var(--lp-gray-600);line-height:1.65;font-weight:400;max-width:560px;margin:0 auto 36px}.lp-hero-ctas{display:flex;align-items:center;justify-content:center;gap:14px;margin-bottom:32px;flex-wrap:wrap}.lp-trust-signals{display:flex;align-items:center;justify-content:center;gap:28px;flex-wrap:wrap}.lp-trust-item{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:500;color:var(--lp-gray-500, #6B7280)}.lp-trust-item svg{color:var(--lp-primary);flex-shrink:0}.lp-section{padding:100px 0;background:var(--lp-white)}.lp-section-alt{background:var(--lp-gray-50)}.lp-section-header{text-align:center;margin-bottom:64px}.lp-section-header h2{font-family:Outfit,sans-serif;font-size:42px;font-weight:800;color:var(--lp-dark);margin:0 0 14px;letter-spacing:-.025em;line-height:1.15}.lp-section-header p{font-size:17px;color:var(--lp-gray-600);max-width:540px;margin:0 auto;line-height:1.65}.lp-features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.lp-feature-card{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:36px 28px;transition:transform .25s var(--lp-ease),box-shadow .25s var(--lp-ease);border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);position:relative;overflow:hidden}.lp-feature-card:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--lp-primary),var(--lp-secondary));transform:scaleX(0);transform-origin:left;transition:transform .3s var(--lp-ease);border-radius:3px 3px 0 0}.lp-feature-card:hover{transform:translateY(-5px);box-shadow:var(--lp-shadow-xl);border-color:transparent}.lp-feature-card:hover:before{transform:scaleX(1)}.lp-feature-icon{width:52px;height:52px;border-radius:var(--lp-radius-sm);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.lp-feature-icon .material-icons{font-size:26px}.lp-feature-title{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-dark);margin:0 0 10px;letter-spacing:-.01em}.lp-feature-desc{font-size:14px;color:var(--lp-gray-600);line-height:1.65;margin:0}.lp-steps-grid{display:flex;align-items:flex-start;justify-content:space-between;position:relative;gap:16px}.lp-step-card{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;padding:0 8px}.lp-step-number{font-size:11px;font-weight:700;color:var(--lp-secondary);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px}.lp-step-icon{width:56px;height:56px;border-radius:50%;background:var(--lp-primary);display:flex;align-items:center;justify-content:center;margin-bottom:18px;box-shadow:0 4px 14px color-mix(in srgb,var(--lp-primary) 30%,transparent);border:3px solid var(--lp-white);position:relative;z-index:1}.lp-step-icon .material-icons{font-size:24px;color:var(--lp-white)}.lp-step-title{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--lp-dark);margin:0 0 8px;letter-spacing:-.01em}.lp-step-desc{font-size:13px;color:var(--lp-gray-600);line-height:1.55;margin:0}.lp-step-connector{position:absolute;top:40px;left:60%;width:80%;height:2px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 0px,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 10px,transparent 10px,transparent 18px);pointer-events:none;z-index:0}.lp-metrics-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:1px solid var(--lp-gray-200);border-radius:var(--lp-radius-lg);overflow:hidden;background:var(--lp-white);box-shadow:var(--lp-shadow)}.lp-metric-card{text-align:center;padding:40px 24px;border-right:1px solid var(--lp-gray-200);transition:background .2s}.lp-metric-card:last-child{border-right:none}.lp-metric-card:hover{background:var(--lp-gray-50)}.lp-metric-value{font-family:Outfit,sans-serif;font-size:48px;font-weight:900;color:var(--lp-primary);line-height:1;letter-spacing:-.03em;margin-bottom:8px}.lp-metric-label{font-size:14px;font-weight:500;color:var(--lp-gray-600)}.lp-modules-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px}.lp-module-group{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:28px 28px 24px;border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);transition:box-shadow .2s}.lp-module-group:hover{box-shadow:var(--lp-shadow-md)}.lp-module-group-label{font-family:Outfit,sans-serif;font-size:11px;font-weight:700;color:var(--lp-primary);margin-bottom:16px;text-transform:uppercase;letter-spacing:1px}.lp-module-tiles{display:flex;flex-wrap:wrap;gap:8px}.lp-module-tile{display:inline-flex;align-items:center;gap:6px;background:var(--lp-gray-50);border:1px solid var(--lp-gray-200);padding:7px 13px;border-radius:100px;font-size:12px;font-weight:600;color:var(--lp-gray-700);transition:all .2s var(--lp-ease);cursor:default}.lp-module-tile:hover{background:color-mix(in srgb,var(--lp-primary) 6%,transparent);border-color:color-mix(in srgb,var(--lp-primary) 25%,transparent);color:var(--lp-primary);transform:translateY(-1px)}.lp-module-tile .material-icons{font-size:15px;color:var(--lp-primary)}.lp-footer{background:var(--lp-dark);padding:44px 0 36px;border-top:1px solid rgba(255,255,255,.04)}.lp-footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:20px}.lp-footer-brand{display:flex;align-items:center;gap:10px}.lp-footer-logo{width:30px;height:30px;border-radius:7px;object-fit:contain;opacity:.9}.lp-footer-name{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-white);letter-spacing:-.01em}.lp-footer-links{display:flex;gap:28px}.lp-footer-links a{font-size:14px;font-weight:500;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-links a:hover{color:var(--lp-white)}.lp-footer-copy{font-size:13px;color:#ffffff59;width:100%;text-align:center;padding-top:20px;margin-top:4px;border-top:1px solid rgba(255,255,255,.06)}.lp-animate{opacity:0;transform:translateY(28px);transition:opacity .65s var(--lp-ease),transform .65s var(--lp-ease)}.lp-visible{opacity:1;transform:translateY(0)}.lp-visible .lp-feature-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-feature-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-feature-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-feature-card:nth-child(4){transition-delay:.18s}.lp-visible .lp-feature-card:nth-child(5){transition-delay:.24s}.lp-visible .lp-feature-card:nth-child(6){transition-delay:.3s}.lp-visible .lp-step-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-step-card:nth-child(2){transition-delay:80ms}.lp-visible .lp-step-card:nth-child(3){transition-delay:.16s}.lp-visible .lp-step-card:nth-child(4){transition-delay:.24s}.lp-visible .lp-metric-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-metric-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-metric-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-metric-card:nth-child(4){transition-delay:.18s}@media (max-width: 1024px){.lp-hero-text h1{font-size:52px}.lp-section-header h2{font-size:36px}.lp-features-grid,.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card:nth-child(2){border-right:none}.lp-metrics-grid .lp-metric-card:nth-child(3){border-top:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:nth-child(4){border-top:1px solid var(--lp-gray-200)}.lp-steps-grid{flex-wrap:wrap;justify-content:center;gap:40px}.lp-step-connector{display:none}}@media (max-width: 767px){.lp-container{padding:0 20px}.lp-nav-links{display:none}.lp-hamburger{display:flex}.lp-mobile-auth{display:none}.lp-mobile-menu{display:none;position:fixed;inset:0;background:#fffffff7;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;align-items:center;justify-content:center;gap:28px;z-index:999}.lp-mobile-menu-open{display:flex}.lp-mobile-menu a{font-size:22px;font-weight:700;color:var(--lp-dark);cursor:pointer;text-decoration:none;letter-spacing:-.01em}.lp-hero-bg{padding:100px 0 60px;min-height:auto}.lp-hero-text h1{font-size:38px}.lp-hero-brand-name{font-size:28px}.lp-hero-subtitle{font-size:16px}.lp-hero-ctas{flex-direction:column}.lp-hero-ctas .lp-btn{width:100%;justify-content:center}.lp-trust-signals{gap:16px}.lp-section{padding:72px 0}.lp-section-header{margin-bottom:40px}.lp-section-header h2{font-size:28px}.lp-features-grid{grid-template-columns:1fr}.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card{border-right:none;border-bottom:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:last-child{border-bottom:none}.lp-metric-value{font-size:36px}.lp-modules-grid{grid-template-columns:1fr}.lp-steps-grid{flex-direction:column;align-items:center;gap:32px}.lp-step-connector{display:none}.lp-footer-inner{flex-direction:column;align-items:center;text-align:center}}.lp-nav-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-nav-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-nav-brand-wordmark{display:flex;flex-direction:column;line-height:1.1;margin-left:8px}.lp-nav-wordmark-line1{font-size:15px;font-weight:800;color:#1a3a7a;letter-spacing:-.01em;font-family:Outfit,sans-serif}.lp-nav-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-hero-bg{position:relative}.lp-hero-watermark{position:absolute;inset:0;display:flex;align-items:flex-start;justify-content:center;padding-top:40px;pointer-events:none;z-index:1}.lp-hero-watermark-inner{width:700px;height:700px;opacity:.04;overflow:hidden;flex-shrink:0}@media (min-width: 768px){.lp-hero-watermark-inner{width:800px;height:800px}}.lp-hero-content{display:flex;flex-direction:column;align-items:center}.lp-hero-main{z-index:1;width:100%}.lp-hero-visual{position:relative;z-index:1;width:100%;max-width:900px;margin:48px auto 0}.lp-workflow-road{position:relative;display:flex;justify-content:space-between;align-items:flex-start;padding:48px 0 24px;gap:16px}.lp-road-line{position:absolute;top:72px;left:6%;right:6%;height:3px;background:linear-gradient(90deg,var(--lp-primary-light),var(--lp-primary),var(--lp-primary-light));border-radius:2px;opacity:.4}.lp-waypoint{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;z-index:1}.lp-waypoint-marker{width:52px;height:52px;border-radius:50%;background:var(--lp-primary);color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:12px;box-shadow:0 4px 16px color-mix(in srgb,var(--lp-primary) 35%,transparent);transition:transform .25s cubic-bezier(.34,1.56,.64,1)}.lp-waypoint:hover .lp-waypoint-marker{transform:translateY(-4px) scale(1.08)}.lp-waypoint-marker .material-icons{font-size:22px}.lp-waypoint-number{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--lp-primary);margin-bottom:6px;opacity:.7}.lp-waypoint-title{font-size:14px;font-weight:700;color:var(--lp-dark);margin-bottom:6px;font-family:Outfit,sans-serif}.lp-waypoint-desc{font-size:12px;color:var(--lp-gray-600);line-height:1.5;max-width:140px}.lp-section-dark{background:var(--lp-dark);color:#fff}.lp-section-dark.lp-section{padding:96px 0}.lp-header-light h2{color:#fff}.lp-header-light p{color:#ffffffa6}.lp-security-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}.lp-security-card{background:#ffffff0d;border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:32px 28px;transition:background .25s ease}.lp-security-card:hover{background:#ffffff14}.lp-security-icon{width:52px;height:52px;border-radius:14px;background:#ffffff1a;display:flex;align-items:center;justify-content:center;margin-bottom:20px;color:#fff}.lp-security-icon .material-icons{font-size:24px}.lp-security-card h3{font-size:18px;font-weight:700;color:#fff;margin:0 0 12px;font-family:Outfit,sans-serif}.lp-security-card p{font-size:14px;color:#fff9;line-height:1.65;margin:0}.lp-testimonials-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px}.lp-testimonial-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:32px 28px;display:flex;flex-direction:column;gap:16px;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-testimonial-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-2px)}.lp-stars{display:flex;gap:2px;color:var(--lp-secondary)}.lp-stars .material-icons{font-size:18px}.lp-testimonial-quote{font-size:15px;color:var(--lp-gray-700);line-height:1.65;font-style:italic;margin:0;flex:1}.lp-testimonial-author{display:flex;align-items:center;gap:12px}.lp-author-avatar{width:44px;height:44px;border-radius:50%;background:var(--lp-primary);color:#fff;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:Outfit,sans-serif}.lp-author-name{font-size:14px;font-weight:700;color:var(--lp-dark)}.lp-author-role{font-size:12px;color:var(--lp-gray-400)}.lp-pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;align-items:start}.lp-pricing-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:36px 28px 28px;position:relative;display:flex;flex-direction:column;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-pricing-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-3px)}.lp-pricing-popular{border-color:var(--lp-primary);box-shadow:0 0 0 2px var(--lp-primary);transform:scale(1.03)}.lp-pricing-popular:hover{transform:scale(1.03) translateY(-3px)}.lp-popular-badge{position:absolute;top:-12px;left:50%;transform:translate(-50%);background:var(--lp-primary);color:#fff;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:4px 14px;border-radius:100px;white-space:nowrap;font-family:Outfit,sans-serif}.lp-pricing-header{margin-bottom:24px}.lp-pricing-header h3{font-size:20px;font-weight:800;color:var(--lp-dark);margin:0 0 12px;font-family:Outfit,sans-serif}.lp-pricing-price{display:flex;align-items:baseline;gap:4px;margin-bottom:8px}.lp-price-amount{font-size:40px;font-weight:900;color:var(--lp-primary);font-family:Outfit,sans-serif;line-height:1}.lp-price-period{font-size:16px;color:var(--lp-gray-600)}.lp-pricing-desc{font-size:13px;color:var(--lp-gray-600);margin:0}.lp-pricing-features{list-style:none;padding:0;margin:0 0 28px;display:flex;flex-direction:column;gap:10px;flex:1}.lp-pricing-features li{font-size:14px;color:var(--lp-gray-700);padding-left:20px;position:relative}.lp-pricing-features li:before{content:\"\\2713\";position:absolute;left:0;color:var(--lp-primary);font-weight:700}.lp-btn-block{width:100%;justify-content:center;text-align:center}.lp-final-cta{background:linear-gradient(135deg,var(--lp-primary-dark, #152C4A) 0%,var(--lp-primary) 100%);padding:96px 0}.lp-cta-content{text-align:center}.lp-cta-content h2{font-size:40px;font-weight:900;color:#fff;margin:0 0 16px;font-family:Outfit,sans-serif;letter-spacing:-.02em}.lp-cta-content p{font-size:18px;color:#ffffffbf;margin:0 auto 36px;max-width:560px}.lp-cta-buttons{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}.lp-btn-white{background:#fff;color:var(--lp-primary);border:2px solid white;font-weight:700}.lp-btn-white:hover{background:#ffffffe6;transform:translateY(-1px)}.lp-btn-outline-white{background:transparent;color:#fff;border:2px solid rgba(255,255,255,.5);font-weight:700}.lp-btn-outline-white:hover{background:#ffffff1a;border-color:#fff;transform:translateY(-1px)}.lp-footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:48px;padding:64px 0 48px}.lp-footer-brand-col{display:flex;flex-direction:column;gap:16px}.lp-footer-brand-custom{display:flex;align-items:center;gap:10px}.lp-footer-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-footer-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-footer-brand-wordmark{display:flex;flex-direction:column;line-height:1.1}.lp-footer-wordmark-line1{font-size:15px;font-weight:800;color:#fff;font-family:Outfit,sans-serif}.lp-footer-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-footer-brand-default{display:flex;align-items:center;gap:10px}.lp-footer-tagline{font-size:14px;color:#ffffff80;line-height:1.6;margin:0;max-width:280px}.lp-footer-col{display:flex;flex-direction:column;gap:12px}.lp-footer-col h4{font-size:13px;font-weight:700;color:#fff;margin:0 0 4px;letter-spacing:.04em;text-transform:uppercase;font-family:Outfit,sans-serif}.lp-footer-col a{font-size:14px;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-col a:hover{color:#fff}.lp-footer-bottom{border-top:1px solid rgba(255,255,255,.08);padding:24px 0;font-size:13px;color:#ffffff59}@media (max-width: 768px){.lp-hero-visual{max-width:100%;margin-top:32px}.lp-workflow-road{flex-direction:column;align-items:center;gap:32px}.lp-road-line{display:none}.lp-waypoint-desc{max-width:260px}.lp-security-grid,.lp-testimonials-grid,.lp-pricing-grid{grid-template-columns:1fr}.lp-pricing-popular{transform:none}.lp-pricing-popular:hover{transform:translateY(-3px)}.lp-cta-content h2{font-size:28px}.lp-footer-grid{grid-template-columns:1fr 1fr;gap:32px;padding:48px 0 32px}}\n"] }]
|
|
14296
|
+
args: [{ selector: 'spa-landing', standalone: false, template: "<!-- ===== NAVBAR ===== -->\r\n<nav class=\"lp-navbar\" [class.lp-navbar-scrolled]=\"navScrolled\">\r\n <div class=\"lp-container lp-nav-inner\">\r\n <!-- Brand: custom SVG override OR default img+name -->\r\n <div class=\"lp-nav-brand\" (click)=\"scrollToSection('lp-hero')\">\r\n <ng-container *ngIf=\"config.navBrand; else defaultNavBrand\">\r\n <span class=\"lp-nav-brand-svg\" [innerHTML]=\"safeNavBrandSvg\"></span>\r\n <div *ngIf=\"config.navBrand.wordmarkLine1\" class=\"lp-nav-brand-wordmark\">\r\n <span class=\"lp-nav-wordmark-line1\">{{ config.navBrand.wordmarkLine1 }}</span>\r\n <span *ngIf=\"config.navBrand.wordmarkLine2\" class=\"lp-nav-wordmark-line2\">{{ config.navBrand.wordmarkLine2 }}</span>\r\n </div>\r\n </ng-container>\r\n <ng-template #defaultNavBrand>\r\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-nav-logo\">\r\n <span class=\"lp-nav-brand-text\">{{ config.appName }}</span>\r\n </ng-template>\r\n </div>\r\n <!-- Desktop nav links -->\r\n <div class=\"lp-nav-links\">\r\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\r\n </div>\r\n <!-- Mobile menu overlay -->\r\n <div class=\"lp-mobile-menu\" [class.lp-mobile-menu-open]=\"mobileMenuOpen\">\r\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\r\n <div class=\"lp-mobile-auth\">\r\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\r\n <button class=\"lp-btn lp-btn-primary\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\r\n </div>\r\n </div>\r\n <!-- Desktop auth -->\r\n <div class=\"lp-nav-auth\">\r\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\r\n <button class=\"lp-btn lp-btn-primary lp-btn-sm\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\r\n </div>\r\n <!-- Hamburger for mobile -->\r\n <button class=\"lp-hamburger\" (click)=\"toggleMobileMenu()\" [class.lp-hamburger-open]=\"mobileMenuOpen\">\r\n <span></span><span></span><span></span>\r\n </button>\r\n </div>\r\n</nav>\r\n\r\n<!-- ===== HERO ===== -->\r\n<section id=\"lp-hero\" class=\"lp-hero-bg\" [class.lp-hero-split]=\"config.hero.layout === 'split'\">\r\n <!-- Optional faded watermark SVG (e.g. company logo) -->\r\n <!-- Changed: wrapper div gets Angular scoping attribute; inner div holds innerHTML so svg size CSS applies to scoped .lp-hero-watermark-inner -->\r\n <div *ngIf=\"config.hero.watermarkSvg\" class=\"lp-hero-watermark\" aria-hidden=\"true\">\r\n <div class=\"lp-hero-watermark-inner\" [innerHTML]=\"safeHeroWatermarkSvg\"></div>\r\n </div>\r\n\r\n <div class=\"lp-container lp-hero-content\">\r\n <!-- Left / centered column: brand, headline, CTAs -->\r\n <div class=\"lp-hero-main\">\r\n <!-- App brand: logo + name + decorative divider -->\r\n <div class=\"lp-hero-brand\">\r\n <div class=\"lp-hero-brand-logo\">\r\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-hero-brand-icon\">\r\n <span class=\"lp-hero-brand-name\">{{ config.appName }}</span>\r\n </div>\r\n <div class=\"lp-hero-divider\" aria-hidden=\"true\">\r\n <span class=\"lp-hero-divider-line\"></span>\r\n <span class=\"lp-hero-divider-dot\"></span>\r\n <span class=\"lp-hero-divider-line\"></span>\r\n </div>\r\n </div>\r\n <!-- Hero text: headline, subtitle, CTAs, trust signals -->\r\n <div class=\"lp-hero-text\">\r\n <div *ngIf=\"config.hero.badge\" class=\"lp-hero-badge\">{{ config.hero.badge }}</div>\r\n <h1>{{ config.hero.headline }} <span class=\"lp-gradient-text\">{{ config.hero.gradientText }}</span></h1>\r\n <p class=\"lp-hero-subtitle\">{{ config.hero.subtitle }}</p>\r\n <div class=\"lp-hero-ctas\">\r\n <button class=\"lp-btn lp-btn-primary lp-btn-lg\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">\r\n {{ config.hero.primaryCTA }}\r\n </button>\r\n <button class=\"lp-btn lp-btn-outline lp-btn-lg\" (click)=\"scrollToSection(config.hero.secondaryCTASection)\">\r\n {{ config.hero.secondaryCTA }}\r\n </button>\r\n </div>\r\n <!-- Trust signals with checkmark SVG icons -->\r\n <div class=\"lp-trust-signals\">\r\n <div *ngFor=\"let signal of config.hero.trustSignals\" class=\"lp-trust-item\">\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\r\n <path d=\"M20 6L9 17l-5-5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n <span>{{ signal }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- App visual: always rendered when projected content exists (stacked below hero text) -->\r\n <!-- Changed: removed *ngIf so visual always renders below text; layout is always stacked/column -->\r\n <div class=\"lp-hero-visual\">\r\n <ng-content select=\"[lpHeroVisual]\"></ng-content>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== METRICS (optional) ===== -->\r\n<section *ngIf=\"config.metrics\" class=\"lp-section lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-metrics-grid\">\r\n <div *ngFor=\"let metric of config.metrics\" class=\"lp-metric-card\">\r\n <div class=\"lp-metric-value\">{{ metric.value }}</div>\r\n <div class=\"lp-metric-label\">{{ metric.label }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== FEATURES ===== -->\r\n<section id=\"lp-features\" class=\"lp-section lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.features.title }}</h2>\r\n <p>{{ config.features.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-features-grid\">\r\n <div *ngFor=\"let feature of config.features.items\" class=\"lp-feature-card\">\r\n <!-- Icon tint: hex + '20' = ~12% opacity background -->\r\n <div class=\"lp-feature-icon\"\r\n [style.background]=\"iconBg(feature.color)\"\r\n [style.color]=\"feature.color\">\r\n <span class=\"material-icons\">{{ feature.icon }}</span>\r\n </div>\r\n <h3 class=\"lp-feature-title\">{{ feature.title }}</h3>\r\n <p class=\"lp-feature-desc\">{{ feature.description }}</p>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== WORKFLOW (optional) ===== -->\r\n<section *ngIf=\"config.workflow\" id=\"lp-workflow\" class=\"lp-section lp-section-alt lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.workflow!.title }}</h2>\r\n <p *ngIf=\"config.workflow!.subtitle\">{{ config.workflow!.subtitle }}</p>\r\n </div>\r\n\r\n <!-- Road metaphor layout: horizontal road line with waypoints -->\r\n <div *ngIf=\"config.workflow!.style === 'road'\" class=\"lp-workflow-road\">\r\n <div class=\"lp-road-line\"></div>\r\n <div *ngFor=\"let step of config.workflow!.steps\" class=\"lp-waypoint\">\r\n <div class=\"lp-waypoint-marker\">\r\n <span class=\"material-icons\">{{ step.icon }}</span>\r\n </div>\r\n <div class=\"lp-waypoint-number\">Step {{ step.number }}</div>\r\n <div class=\"lp-waypoint-title\">{{ step.title }}</div>\r\n <div class=\"lp-waypoint-desc\">{{ step.description }}</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default steps grid layout -->\r\n <div *ngIf=\"!config.workflow!.style || config.workflow!.style === 'steps'\" class=\"lp-steps-grid\">\r\n <div *ngFor=\"let step of config.workflow!.steps; let i = index\" class=\"lp-step-card\">\r\n <div class=\"lp-step-number\">{{ step.number }}</div>\r\n <div class=\"lp-step-icon\">\r\n <span class=\"material-icons\">{{ step.icon }}</span>\r\n </div>\r\n <h3 class=\"lp-step-title\">{{ step.title }}</h3>\r\n <p class=\"lp-step-desc\">{{ step.description }}</p>\r\n <!-- Connector line between steps, not after last -->\r\n <div *ngIf=\"i < config.workflow!.steps.length - 1\" class=\"lp-step-connector\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== CUSTOM SECTION SLOT ===== -->\r\n<!-- Consumer projects app-specific sections here (fleet board, benefits rows, etc.)\r\n using [lpCustomSection] attribute on a wrapper element -->\r\n<ng-content select=\"[lpCustomSection]\"></ng-content>\r\n\r\n<!-- ===== MODULES (optional) ===== -->\r\n<section *ngIf=\"config.modules\" id=\"lp-modules\" class=\"lp-section lp-section-alt lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.modules!.title }}</h2>\r\n <p>{{ config.modules!.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-modules-grid\">\r\n <div *ngFor=\"let group of config.modules!.groups\" class=\"lp-module-group\">\r\n <div class=\"lp-module-group-label\">{{ group.category }}</div>\r\n <div class=\"lp-module-tiles\">\r\n <div *ngFor=\"let tile of group.tiles\" class=\"lp-module-tile\">\r\n <span class=\"material-icons\">{{ tile.icon }}</span>\r\n <span>{{ tile.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== SECURITY (optional \u2014 dark background) ===== -->\r\n<section *ngIf=\"config.security\" class=\"lp-section lp-section-dark lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header lp-header-light\">\r\n <h2>{{ config.security!.title }}</h2>\r\n <p>{{ config.security!.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-security-grid\">\r\n <div *ngFor=\"let item of config.security!.items\" class=\"lp-security-card\">\r\n <div class=\"lp-security-icon\">\r\n <span class=\"material-icons\">{{ item.icon }}</span>\r\n </div>\r\n <h3>{{ item.title }}</h3>\r\n <p>{{ item.description }}</p>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== TESTIMONIALS (optional) ===== -->\r\n<section *ngIf=\"config.testimonials\" class=\"lp-section lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.testimonials!.title }}</h2>\r\n <p *ngIf=\"config.testimonials!.subtitle\">{{ config.testimonials!.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-testimonials-grid\">\r\n <div *ngFor=\"let item of config.testimonials!.items\" class=\"lp-testimonial-card\">\r\n <div class=\"lp-stars\">\r\n <span class=\"material-icons\">star</span>\r\n <span class=\"material-icons\">star</span>\r\n <span class=\"material-icons\">star</span>\r\n <span class=\"material-icons\">star</span>\r\n <span class=\"material-icons\">star</span>\r\n </div>\r\n <p class=\"lp-testimonial-quote\">\"{{ item.quote }}\"</p>\r\n <div class=\"lp-testimonial-author\">\r\n <div class=\"lp-author-avatar\">{{ item.authorInitials }}</div>\r\n <div>\r\n <div class=\"lp-author-name\">{{ item.authorName }}</div>\r\n <div class=\"lp-author-role\">{{ item.authorRole }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== PRICING (optional) ===== -->\r\n<section *ngIf=\"config.pricing\" id=\"lp-pricing\" class=\"lp-section lp-animate\">\r\n <div class=\"lp-container\">\r\n <div class=\"lp-section-header\">\r\n <h2>{{ config.pricing!.title }}</h2>\r\n <p *ngIf=\"config.pricing!.subtitle\">{{ config.pricing!.subtitle }}</p>\r\n </div>\r\n <div class=\"lp-pricing-grid\">\r\n <div *ngFor=\"let plan of config.pricing!.plans\"\r\n class=\"lp-pricing-card\"\r\n [class.lp-pricing-popular]=\"plan.popular\">\r\n <div *ngIf=\"plan.popular\" class=\"lp-popular-badge\">Most Popular</div>\r\n <div class=\"lp-pricing-header\">\r\n <h3>{{ plan.name }}</h3>\r\n <div class=\"lp-pricing-price\">\r\n <span class=\"lp-price-amount\">{{ plan.price }}</span>\r\n <span *ngIf=\"plan.period\" class=\"lp-price-period\">{{ plan.period }}</span>\r\n </div>\r\n <p class=\"lp-pricing-desc\">{{ plan.description }}</p>\r\n </div>\r\n <ul class=\"lp-pricing-features\">\r\n <li *ngFor=\"let feature of plan.features\">{{ feature }}</li>\r\n </ul>\r\n <!-- Popular plans get primary button, others get outline -->\r\n <button *ngIf=\"plan.popular\"\r\n class=\"lp-btn lp-btn-primary lp-btn-block\"\r\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\r\n {{ plan.ctaLabel }}\r\n </button>\r\n <button *ngIf=\"!plan.popular\"\r\n class=\"lp-btn lp-btn-outline lp-btn-block\"\r\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\r\n {{ plan.ctaLabel }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== FINAL CTA BANNER (optional) ===== -->\r\n<section *ngIf=\"config.finalCta\" class=\"lp-final-cta lp-animate\">\r\n <div class=\"lp-container lp-cta-content\">\r\n <h2>{{ config.finalCta!.headline }}</h2>\r\n <p>{{ config.finalCta!.subtitle }}</p>\r\n <div class=\"lp-cta-buttons\">\r\n <button class=\"lp-btn lp-btn-white lp-btn-lg\"\r\n (click)=\"navigateTo(config.finalCta!.primaryCTARoute || 'signup')\">\r\n {{ config.finalCta!.primaryCTA }}\r\n </button>\r\n <button *ngIf=\"config.finalCta!.secondaryCTA\"\r\n class=\"lp-btn lp-btn-outline-white lp-btn-lg\"\r\n (click)=\"scrollToSection(config.finalCta!.secondaryCTASection || '')\">\r\n {{ config.finalCta!.secondaryCTA }}\r\n </button>\r\n </div>\r\n </div>\r\n</section>\r\n\r\n<!-- ===== FOOTER ===== -->\r\n<footer class=\"lp-footer\" id=\"lp-footer\">\r\n <div class=\"lp-container\">\r\n\r\n <!-- Rich multi-column footer when footerColumns provided -->\r\n <div *ngIf=\"config.footerColumns && config.footerColumns.length; else simpleFooter\" class=\"lp-footer-grid\">\r\n <!-- Brand column -->\r\n <div class=\"lp-footer-brand-col\">\r\n <!-- Custom SVG footer brand OR default img+name -->\r\n <ng-container *ngIf=\"config.footerBrand; else defaultFooterBrand\">\r\n <div class=\"lp-footer-brand-custom\">\r\n <span class=\"lp-footer-brand-svg\" [innerHTML]=\"safeFooterBrandSvg\"></span>\r\n <div *ngIf=\"config.footerBrand.wordmarkLine1\" class=\"lp-footer-brand-wordmark\">\r\n <span class=\"lp-footer-wordmark-line1\">{{ config.footerBrand.wordmarkLine1 }}</span>\r\n <span *ngIf=\"config.footerBrand.wordmarkLine2\" class=\"lp-footer-wordmark-line2\">{{ config.footerBrand.wordmarkLine2 }}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #defaultFooterBrand>\r\n <div class=\"lp-footer-brand-default\">\r\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\r\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\r\n </div>\r\n </ng-template>\r\n <p *ngIf=\"config.footerTagline\" class=\"lp-footer-tagline\">{{ config.footerTagline }}</p>\r\n </div>\r\n <!-- Link columns -->\r\n <div *ngFor=\"let col of config.footerColumns\" class=\"lp-footer-col\">\r\n <h4>{{ col.title }}</h4>\r\n <a *ngFor=\"let link of col.links\"\r\n (click)=\"link.section ? scrollToSection(link.section) : (link.route ? navigateTo(link.route) : null)\">\r\n {{ link.label }}\r\n </a>\r\n </div>\r\n </div>\r\n\r\n <!-- Simple single-row footer (default) -->\r\n <ng-template #simpleFooter>\r\n <div class=\"lp-footer-inner\">\r\n <div class=\"lp-footer-brand\">\r\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\r\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\r\n </div>\r\n <div class=\"lp-footer-links\">\r\n <a (click)=\"navigateTo('login')\">Log in</a>\r\n <a (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Sign up</a>\r\n </div>\r\n <div class=\"lp-footer-copy\">\r\n © {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Footer bottom bar (shown for both layouts) -->\r\n <div *ngIf=\"config.footerColumns && config.footerColumns.length\" class=\"lp-footer-bottom\">\r\n <span>© {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.</span>\r\n </div>\r\n </div>\r\n</footer>\r\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap\";:host{--lp-primary: #1E3A5F;--lp-primary-dark: #152C4A;--lp-primary-light: #EBF0F7;--lp-secondary: #F59E0B;--lp-secondary-light:#FEF3C7;--lp-dark: #0D1117;--lp-gray-800: #1F2937;--lp-gray-700: #374151;--lp-gray-600: #4B5563;--lp-gray-400: #9CA3AF;--lp-gray-200: #E5E7EB;--lp-gray-100: #F3F4F6;--lp-gray-50: #F9FAFB;--lp-white: #FFFFFF;--lp-radius: 12px;--lp-radius-sm: 8px;--lp-radius-lg: 20px;--lp-radius-xl: 28px;--lp-shadow-xs: 0 1px 2px rgba(0,0,0,.05);--lp-shadow: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.05);--lp-shadow-md: 0 4px 8px rgba(0,0,0,.06), 0 2px 4px rgba(0,0,0,.04);--lp-shadow-lg: 0 12px 28px rgba(0,0,0,.1), 0 4px 8px rgba(0,0,0,.06);--lp-shadow-xl: 0 20px 40px rgba(0,0,0,.12), 0 8px 16px rgba(0,0,0,.07);--lp-ease: cubic-bezier(.4, 0, .2, 1);--lp-spring: cubic-bezier(.34, 1.56, .64, 1);display:block;font-family:Outfit,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--lp-gray-700);line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased}.lp-container{max-width:1200px;margin:0 auto;padding:0 32px}.lp-navbar{position:fixed;top:0;left:0;right:0;z-index:1000;padding:18px 0;transition:background .3s var(--lp-ease),padding .3s var(--lp-ease),box-shadow .3s var(--lp-ease)}.lp-navbar-scrolled{background:#ffffffeb;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);box-shadow:0 1px #0000000f,0 2px 8px #0000000a;padding:12px 0}.lp-nav-inner{display:flex;align-items:center;justify-content:space-between}.lp-nav-brand{display:flex;align-items:center;gap:10px;cursor:pointer;text-decoration:none}.lp-nav-logo{width:30px;height:30px;border-radius:7px;object-fit:contain}.lp-nav-brand-text{font-size:18px;font-weight:700;color:var(--lp-primary);letter-spacing:-.01em}.lp-nav-links{display:flex;align-items:center;gap:36px}.lp-nav-links a{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none;letter-spacing:.01em}.lp-nav-links a:hover{color:var(--lp-primary)}.lp-nav-auth{display:flex;align-items:center;gap:12px}.lp-nav-login{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none}.lp-nav-login:hover{color:var(--lp-primary)}.lp-hamburger{display:none;flex-direction:column;gap:5px;background:none;border:none;cursor:pointer;padding:4px;z-index:1001}.lp-hamburger span{display:block;width:22px;height:2px;background:var(--lp-gray-700);border-radius:2px;transition:transform .3s var(--lp-ease),opacity .3s}.lp-hamburger-open span:nth-child(1){transform:translateY(7px) rotate(45deg)}.lp-hamburger-open span:nth-child(2){opacity:0}.lp-hamburger-open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.lp-mobile-menu{display:none}.lp-mobile-auth{display:flex;flex-direction:column;gap:12px;margin-top:24px;padding-top:24px;border-top:1px solid var(--lp-gray-200)}.lp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:Outfit,sans-serif;font-size:14px;font-weight:600;padding:10px 22px;border-radius:var(--lp-radius-sm);border:none;cursor:pointer;transition:all .25s var(--lp-ease);text-decoration:none;white-space:nowrap;letter-spacing:.01em}.lp-btn-primary{background:var(--lp-primary);color:var(--lp-white);box-shadow:0 1px 3px #00000026,inset 0 1px #ffffff1f}.lp-btn-primary:hover{background:var(--lp-primary-dark);transform:translateY(-1px);box-shadow:0 6px 16px #0003,inset 0 1px #ffffff1f}.lp-btn-primary:active{transform:translateY(0)}.lp-btn-outline{background:transparent;color:var(--lp-primary);border:1.5px solid var(--lp-primary)}.lp-btn-outline:hover{background:var(--lp-primary);color:var(--lp-white);transform:translateY(-1px);box-shadow:0 4px 12px #00000026}.lp-btn-sm{padding:7px 16px;font-size:13px}.lp-btn-lg{padding:14px 32px;font-size:15px;border-radius:var(--lp-radius)}.lp-btn-block{width:100%}.lp-hero-bg{position:relative;overflow:hidden;padding:140px 0 100px;background:radial-gradient(ellipse 80% 60% at 60% -10%,color-mix(in srgb,var(--lp-primary) 8%,transparent) 0%,transparent 70%),radial-gradient(ellipse 60% 50% at -10% 80%,color-mix(in srgb,var(--lp-secondary) 6%,transparent) 0%,transparent 70%),linear-gradient(160deg,var(--lp-primary-light) 0%,#FAFBFF 45%,var(--lp-secondary-light) 100%);min-height:100vh;display:flex;align-items:center}.lp-hero-bg:before{content:\"\";position:absolute;inset:0;background-image:radial-gradient(circle,rgba(0,0,0,.04) 1px,transparent 1px);background-size:32px 32px;pointer-events:none;-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%);mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%)}.lp-hero-bg:after{content:\"\";position:absolute;width:700px;height:700px;border-radius:50%;background:color-mix(in srgb,var(--lp-primary) 5%,transparent);top:-200px;right:-150px;filter:blur(100px);pointer-events:none}.lp-hero-content{position:relative;z-index:1;text-align:center;width:100%}.lp-hero-badge{display:inline-flex;align-items:center;gap:6px;background:color-mix(in srgb,var(--lp-primary) 10%,transparent);color:var(--lp-primary);border:1px solid color-mix(in srgb,var(--lp-primary) 20%,transparent);border-radius:100px;padding:6px 16px;font-size:13px;font-weight:600;margin-bottom:20px;letter-spacing:.02em}.lp-hero-brand{margin-bottom:20px;text-align:center}.lp-hero-brand-logo{display:inline-flex;align-items:center;gap:14px;margin-bottom:12px}.lp-hero-brand-icon{height:52px;width:52px;border-radius:14px;object-fit:contain;box-shadow:0 4px 12px #0000001f}.lp-hero-brand-name{font-size:38px;font-weight:800;color:var(--lp-primary);letter-spacing:-.03em;font-family:Outfit,sans-serif}.lp-hero-divider{display:flex;align-items:center;justify-content:center;gap:10px}.lp-hero-divider-line{display:block;height:1px;width:40px;background:linear-gradient(90deg,transparent,var(--lp-primary),transparent);opacity:.3}.lp-hero-divider-dot{display:block;height:5px;width:5px;border-radius:50%;background:var(--lp-secondary);opacity:.8}.lp-hero-text{max-width:760px;margin:0 auto 64px}.lp-hero-text h1{font-family:Outfit,sans-serif;font-size:68px;font-weight:900;line-height:1.05;color:var(--lp-dark);margin:0 0 24px;letter-spacing:-.03em}.lp-gradient-text{background:linear-gradient(135deg,var(--lp-primary) 0%,var(--lp-secondary) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.lp-hero-subtitle{font-size:19px;color:var(--lp-gray-600);line-height:1.65;font-weight:400;max-width:560px;margin:0 auto 36px}.lp-hero-ctas{display:flex;align-items:center;justify-content:center;gap:14px;margin-bottom:32px;flex-wrap:wrap}.lp-trust-signals{display:flex;align-items:center;justify-content:center;gap:28px;flex-wrap:wrap}.lp-trust-item{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:500;color:var(--lp-gray-500, #6B7280)}.lp-trust-item svg{color:var(--lp-primary);flex-shrink:0}.lp-section{padding:100px 0;background:var(--lp-white)}.lp-section-alt{background:var(--lp-gray-50)}.lp-section-header{text-align:center;margin-bottom:64px}.lp-section-header h2{font-family:Outfit,sans-serif;font-size:42px;font-weight:800;color:var(--lp-dark);margin:0 0 14px;letter-spacing:-.025em;line-height:1.15}.lp-section-header p{font-size:17px;color:var(--lp-gray-600);max-width:540px;margin:0 auto;line-height:1.65}.lp-features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.lp-feature-card{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:36px 28px;transition:transform .25s var(--lp-ease),box-shadow .25s var(--lp-ease);border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);position:relative;overflow:hidden}.lp-feature-card:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--lp-primary),var(--lp-secondary));transform:scaleX(0);transform-origin:left;transition:transform .3s var(--lp-ease);border-radius:3px 3px 0 0}.lp-feature-card:hover{transform:translateY(-5px);box-shadow:var(--lp-shadow-xl);border-color:transparent}.lp-feature-card:hover:before{transform:scaleX(1)}.lp-feature-icon{width:52px;height:52px;border-radius:var(--lp-radius-sm);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.lp-feature-icon .material-icons{font-size:26px}.lp-feature-title{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-dark);margin:0 0 10px;letter-spacing:-.01em}.lp-feature-desc{font-size:14px;color:var(--lp-gray-600);line-height:1.65;margin:0}.lp-steps-grid{display:flex;align-items:flex-start;justify-content:space-between;position:relative;gap:16px}.lp-step-card{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;padding:0 8px}.lp-step-number{font-size:11px;font-weight:700;color:var(--lp-secondary);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px}.lp-step-icon{width:56px;height:56px;border-radius:50%;background:var(--lp-primary);display:flex;align-items:center;justify-content:center;margin-bottom:18px;box-shadow:0 4px 14px color-mix(in srgb,var(--lp-primary) 30%,transparent);border:3px solid var(--lp-white);position:relative;z-index:1}.lp-step-icon .material-icons{font-size:24px;color:var(--lp-white)}.lp-step-title{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--lp-dark);margin:0 0 8px;letter-spacing:-.01em}.lp-step-desc{font-size:13px;color:var(--lp-gray-600);line-height:1.55;margin:0}.lp-step-connector{position:absolute;top:40px;left:60%;width:80%;height:2px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 0px,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 10px,transparent 10px,transparent 18px);pointer-events:none;z-index:0}.lp-metrics-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:1px solid var(--lp-gray-200);border-radius:var(--lp-radius-lg);overflow:hidden;background:var(--lp-white);box-shadow:var(--lp-shadow)}.lp-metric-card{text-align:center;padding:40px 24px;border-right:1px solid var(--lp-gray-200);transition:background .2s}.lp-metric-card:last-child{border-right:none}.lp-metric-card:hover{background:var(--lp-gray-50)}.lp-metric-value{font-family:Outfit,sans-serif;font-size:48px;font-weight:900;color:var(--lp-primary);line-height:1;letter-spacing:-.03em;margin-bottom:8px}.lp-metric-label{font-size:14px;font-weight:500;color:var(--lp-gray-600)}.lp-modules-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px}.lp-module-group{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:28px 28px 24px;border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);transition:box-shadow .2s}.lp-module-group:hover{box-shadow:var(--lp-shadow-md)}.lp-module-group-label{font-family:Outfit,sans-serif;font-size:11px;font-weight:700;color:var(--lp-primary);margin-bottom:16px;text-transform:uppercase;letter-spacing:1px}.lp-module-tiles{display:flex;flex-wrap:wrap;gap:8px}.lp-module-tile{display:inline-flex;align-items:center;gap:6px;background:var(--lp-gray-50);border:1px solid var(--lp-gray-200);padding:7px 13px;border-radius:100px;font-size:12px;font-weight:600;color:var(--lp-gray-700);transition:all .2s var(--lp-ease);cursor:default}.lp-module-tile:hover{background:color-mix(in srgb,var(--lp-primary) 6%,transparent);border-color:color-mix(in srgb,var(--lp-primary) 25%,transparent);color:var(--lp-primary);transform:translateY(-1px)}.lp-module-tile .material-icons{font-size:15px;color:var(--lp-primary)}.lp-footer{background:var(--lp-dark);padding:44px 0 36px;border-top:1px solid rgba(255,255,255,.04)}.lp-footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:20px}.lp-footer-brand{display:flex;align-items:center;gap:10px}.lp-footer-logo{width:30px;height:30px;border-radius:7px;object-fit:contain;opacity:.9}.lp-footer-name{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-white);letter-spacing:-.01em}.lp-footer-links{display:flex;gap:28px}.lp-footer-links a{font-size:14px;font-weight:500;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-links a:hover{color:var(--lp-white)}.lp-footer-copy{font-size:13px;color:#ffffff59;width:100%;text-align:center;padding-top:20px;margin-top:4px;border-top:1px solid rgba(255,255,255,.06)}.lp-animate{opacity:0;transform:translateY(28px);transition:opacity .65s var(--lp-ease),transform .65s var(--lp-ease)}.lp-visible{opacity:1;transform:translateY(0)}.lp-visible .lp-feature-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-feature-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-feature-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-feature-card:nth-child(4){transition-delay:.18s}.lp-visible .lp-feature-card:nth-child(5){transition-delay:.24s}.lp-visible .lp-feature-card:nth-child(6){transition-delay:.3s}.lp-visible .lp-step-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-step-card:nth-child(2){transition-delay:80ms}.lp-visible .lp-step-card:nth-child(3){transition-delay:.16s}.lp-visible .lp-step-card:nth-child(4){transition-delay:.24s}.lp-visible .lp-metric-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-metric-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-metric-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-metric-card:nth-child(4){transition-delay:.18s}@media (max-width: 1024px){.lp-hero-text h1{font-size:52px}.lp-section-header h2{font-size:36px}.lp-features-grid,.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card:nth-child(2){border-right:none}.lp-metrics-grid .lp-metric-card:nth-child(3){border-top:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:nth-child(4){border-top:1px solid var(--lp-gray-200)}.lp-steps-grid{flex-wrap:wrap;justify-content:center;gap:40px}.lp-step-connector{display:none}}@media (max-width: 767px){.lp-container{padding:0 20px}.lp-nav-links{display:none}.lp-hamburger{display:flex}.lp-mobile-auth{display:none}.lp-mobile-menu{display:none;position:fixed;inset:0;background:#fffffff7;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;align-items:center;justify-content:center;gap:28px;z-index:999}.lp-mobile-menu-open{display:flex}.lp-mobile-menu a{font-size:22px;font-weight:700;color:var(--lp-dark);cursor:pointer;text-decoration:none;letter-spacing:-.01em}.lp-hero-bg{padding:100px 0 60px;min-height:auto}.lp-hero-text h1{font-size:38px}.lp-hero-brand-name{font-size:28px}.lp-hero-subtitle{font-size:16px}.lp-hero-ctas{flex-direction:column}.lp-hero-ctas .lp-btn{width:100%;justify-content:center}.lp-trust-signals{gap:16px}.lp-section{padding:72px 0}.lp-section-header{margin-bottom:40px}.lp-section-header h2{font-size:28px}.lp-features-grid{grid-template-columns:1fr}.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card{border-right:none;border-bottom:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:last-child{border-bottom:none}.lp-metric-value{font-size:36px}.lp-modules-grid{grid-template-columns:1fr}.lp-steps-grid{flex-direction:column;align-items:center;gap:32px}.lp-step-connector{display:none}.lp-footer-inner{flex-direction:column;align-items:center;text-align:center}}.lp-nav-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-nav-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-nav-brand-wordmark{display:flex;flex-direction:column;line-height:1.1;margin-left:8px}.lp-nav-wordmark-line1{font-size:15px;font-weight:800;color:#1a3a7a;letter-spacing:-.01em;font-family:Outfit,sans-serif}.lp-nav-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-hero-bg{position:relative}.lp-hero-watermark{position:absolute;inset:0;display:flex;align-items:flex-start;justify-content:center;padding-top:40px;pointer-events:none;z-index:1}.lp-hero-watermark-inner{width:700px;height:700px;opacity:.04;overflow:hidden;flex-shrink:0}@media (min-width: 768px){.lp-hero-watermark-inner{width:800px;height:800px}}.lp-hero-content{display:flex;flex-direction:column;align-items:center}.lp-hero-main{z-index:1;width:100%}.lp-hero-visual{position:relative;z-index:1;width:100%;max-width:900px;margin:48px auto 0}.lp-workflow-road{position:relative;display:flex;justify-content:space-between;align-items:flex-start;padding:48px 0 24px;gap:16px}.lp-road-line{position:absolute;top:72px;left:6%;right:6%;height:3px;background:linear-gradient(90deg,var(--lp-primary-light),var(--lp-primary),var(--lp-primary-light));border-radius:2px;opacity:.4}.lp-waypoint{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;z-index:1}.lp-waypoint-marker{width:52px;height:52px;border-radius:50%;background:var(--lp-primary);color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:12px;box-shadow:0 4px 16px color-mix(in srgb,var(--lp-primary) 35%,transparent);transition:transform .25s cubic-bezier(.34,1.56,.64,1)}.lp-waypoint:hover .lp-waypoint-marker{transform:translateY(-4px) scale(1.08)}.lp-waypoint-marker .material-icons{font-size:22px}.lp-waypoint-number{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--lp-primary);margin-bottom:6px;opacity:.7}.lp-waypoint-title{font-size:14px;font-weight:700;color:var(--lp-dark);margin-bottom:6px;font-family:Outfit,sans-serif}.lp-waypoint-desc{font-size:12px;color:var(--lp-gray-600);line-height:1.5;max-width:140px}.lp-section-dark{background:var(--lp-dark);color:#fff}.lp-section-dark.lp-section{padding:96px 0}.lp-header-light h2{color:#fff}.lp-header-light p{color:#ffffffa6}.lp-security-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}.lp-security-card{background:#ffffff0d;border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:32px 28px;transition:background .25s ease}.lp-security-card:hover{background:#ffffff14}.lp-security-icon{width:52px;height:52px;border-radius:14px;background:#ffffff1a;display:flex;align-items:center;justify-content:center;margin-bottom:20px;color:#fff}.lp-security-icon .material-icons{font-size:24px}.lp-security-card h3{font-size:18px;font-weight:700;color:#fff;margin:0 0 12px;font-family:Outfit,sans-serif}.lp-security-card p{font-size:14px;color:#fff9;line-height:1.65;margin:0}.lp-testimonials-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px}.lp-testimonial-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:32px 28px;display:flex;flex-direction:column;gap:16px;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-testimonial-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-2px)}.lp-stars{display:flex;gap:2px;color:var(--lp-secondary)}.lp-stars .material-icons{font-size:18px}.lp-testimonial-quote{font-size:15px;color:var(--lp-gray-700);line-height:1.65;font-style:italic;margin:0;flex:1}.lp-testimonial-author{display:flex;align-items:center;gap:12px}.lp-author-avatar{width:44px;height:44px;border-radius:50%;background:var(--lp-primary);color:#fff;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:Outfit,sans-serif}.lp-author-name{font-size:14px;font-weight:700;color:var(--lp-dark)}.lp-author-role{font-size:12px;color:var(--lp-gray-400)}.lp-pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;align-items:start}.lp-pricing-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:36px 28px 28px;position:relative;display:flex;flex-direction:column;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-pricing-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-3px)}.lp-pricing-popular{border-color:var(--lp-primary);box-shadow:0 0 0 2px var(--lp-primary);transform:scale(1.03)}.lp-pricing-popular:hover{transform:scale(1.03) translateY(-3px)}.lp-popular-badge{position:absolute;top:-12px;left:50%;transform:translate(-50%);background:var(--lp-primary);color:#fff;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:4px 14px;border-radius:100px;white-space:nowrap;font-family:Outfit,sans-serif}.lp-pricing-header{margin-bottom:24px}.lp-pricing-header h3{font-size:20px;font-weight:800;color:var(--lp-dark);margin:0 0 12px;font-family:Outfit,sans-serif}.lp-pricing-price{display:flex;align-items:baseline;gap:4px;margin-bottom:8px}.lp-price-amount{font-size:40px;font-weight:900;color:var(--lp-primary);font-family:Outfit,sans-serif;line-height:1}.lp-price-period{font-size:16px;color:var(--lp-gray-600)}.lp-pricing-desc{font-size:13px;color:var(--lp-gray-600);margin:0}.lp-pricing-features{list-style:none;padding:0;margin:0 0 28px;display:flex;flex-direction:column;gap:10px;flex:1}.lp-pricing-features li{font-size:14px;color:var(--lp-gray-700);padding-left:20px;position:relative}.lp-pricing-features li:before{content:\"\\2713\";position:absolute;left:0;color:var(--lp-primary);font-weight:700}.lp-btn-block{width:100%;justify-content:center;text-align:center}.lp-final-cta{background:linear-gradient(135deg,var(--lp-primary-dark, #152C4A) 0%,var(--lp-primary) 100%);padding:96px 0}.lp-cta-content{text-align:center}.lp-cta-content h2{font-size:40px;font-weight:900;color:#fff;margin:0 0 16px;font-family:Outfit,sans-serif;letter-spacing:-.02em}.lp-cta-content p{font-size:18px;color:#ffffffbf;margin:0 auto 36px;max-width:560px}.lp-cta-buttons{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}.lp-btn-white{background:#fff;color:var(--lp-primary);border:2px solid white;font-weight:700}.lp-btn-white:hover{background:#ffffffe6;transform:translateY(-1px)}.lp-btn-outline-white{background:transparent;color:#fff;border:2px solid rgba(255,255,255,.5);font-weight:700}.lp-btn-outline-white:hover{background:#ffffff1a;border-color:#fff;transform:translateY(-1px)}.lp-footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:48px;padding:64px 0 48px}.lp-footer-brand-col{display:flex;flex-direction:column;gap:16px}.lp-footer-brand-custom{display:flex;align-items:center;gap:10px}.lp-footer-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-footer-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-footer-brand-wordmark{display:flex;flex-direction:column;line-height:1.1}.lp-footer-wordmark-line1{font-size:15px;font-weight:800;color:#fff;font-family:Outfit,sans-serif}.lp-footer-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-footer-brand-default{display:flex;align-items:center;gap:10px}.lp-footer-tagline{font-size:14px;color:#ffffff80;line-height:1.6;margin:0;max-width:280px}.lp-footer-col{display:flex;flex-direction:column;gap:12px}.lp-footer-col h4{font-size:13px;font-weight:700;color:#fff;margin:0 0 4px;letter-spacing:.04em;text-transform:uppercase;font-family:Outfit,sans-serif}.lp-footer-col a{font-size:14px;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-col a:hover{color:#fff}.lp-footer-bottom{border-top:1px solid rgba(255,255,255,.08);padding:24px 0;font-size:13px;color:#ffffff59}@media (max-width: 768px){.lp-hero-visual{max-width:100%;margin-top:32px}.lp-workflow-road{flex-direction:column;align-items:center;gap:32px}.lp-road-line{display:none}.lp-waypoint-desc{max-width:260px}.lp-security-grid,.lp-testimonials-grid,.lp-pricing-grid{grid-template-columns:1fr}.lp-pricing-popular{transform:none}.lp-pricing-popular:hover{transform:translateY(-3px)}.lp-cta-content h2{font-size:28px}.lp-footer-grid{grid-template-columns:1fr 1fr;gap:32px;padding:48px 0 32px}}\n"] }]
|
|
14191
14297
|
}], ctorParameters: () => [{ type: i1$2.Router }, { type: AuthService }, { type: i0.ElementRef }, { type: i1$5.DomSanitizer }], propDecorators: { config: [{
|
|
14192
14298
|
type: Input
|
|
14193
14299
|
}], onWindowScroll: [{
|
|
@@ -14552,11 +14658,11 @@ class LoginComponent {
|
|
|
14552
14658
|
}
|
|
14553
14659
|
}
|
|
14554
14660
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LoginComponent, deps: [{ token: HttpService }, { token: StorageService }, { token: i1$2.Router }, { token: MessageService }, { token: DataServiceLib }, { token: AuthService }, { token: LogService }, { token: i1$2.ActivatedRoute }, { token: NotificationsService }, { token: SignalRService }, { token: i10.MsalService }, { token: i1.MatDialog }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
14555
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: LoginComponent, isStandalone: false, selector: "spa-login", ngImport: i0, template: "\r\n <div *ngIf=\"style=='default'\" class=\"login-page background tin-bg-login\">\r\n\r\n <div class=\"container\" >\r\n\r\n <div class=\"logo\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <mat-card class=\"mat-elevation-z3 \" style=\"width:400px; \">\r\n\r\n <mat-card-header style=\"margin-bottom: 30px;margin-top: 30px;\">\r\n <mat-card-title style=\"font-size: 40px;margin-bottom: 10px; margin-top: 20px; font-weight: 300\">{{appConfig.appName}}</mat-card-title>\r\n </mat-card-header>\r\n\r\n <mat-card-content *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n\r\n <div class=\"tin-input mt-2\">\r\n\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: Added \"Keep me signed in\" checkbox -->\r\n <mat-checkbox [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n </div>\r\n\r\n </mat-card-content>\r\n\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n\r\n <div class=\"button mt-0\" *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n <button id=\"btnLogin\" mat-flat-button [disabled]=\"isProcessing\" style=\"width: 350px;\" (click)=\"login()\" color=\"primary\">Login</button>\r\n </div>\r\n\r\n <!-- Changed: Show signup button whenever selfService is enabled, not just for local/AD auth -->\r\n <div class=\"button\" *ngIf=\"appConfig.selfService\" >\r\n <button id=\"btnSignup\" mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"signup()\">Create an account</button>\r\n </div>\r\n\r\n <div class=\"divider\" *ngIf=\"appConfig.googleAuth || appConfig.microsoftAuth\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.googleAuth\">\r\n <asl-google-signin-button type='standard' width=\"320px\" size='medium' logo_alignment=\"center\" style=\"text-align: center;\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" style=\"margin-top: 1em\" (click)=\"recoverAccount()\">Forgot your password ?</a>\r\n\r\n\r\n\r\n\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"style=='modern'\" class=\"modern-login\">\r\n <mat-card class=\"login-card\">\r\n <!-- Logo -->\r\n <div class=\"logo-container\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <!-- Welcome text -->\r\n <div class=\"header-section\"> <!-- Changed: Use custom div instead of mat-card-header -->\r\n <h2 class=\"login-title\">{{appConfig.loginTitle ?? appConfig.appName}}</h2> <!-- Changed: Use h2 for title -->\r\n <p class=\"login-subtitle\" *ngIf=\"appConfig.loginMessage\">{{appConfig.loginMessage}}</p> <!-- Changed: Use p for subtitle -->\r\n </div>\r\n\r\n <mat-card-content>\r\n <div class=\"login-form\">\r\n <!-- Username -->\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" [appearance]=\"'outline'\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <!-- Password -->\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" [appearance]=\"'outline'\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: Added \"Keep me signed in\" checkbox -->\r\n <mat-checkbox [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px; margin-bottom: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n <!-- Login Button -->\r\n <div class=\"button-container\">\r\n <button id=\"btnLogin\" mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"login()\">\r\n Login\r\n </button>\r\n </div>\r\n\r\n <!-- Divider -->\r\n <div class=\"divider\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Social Login -->\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"social-login\">\r\n <asl-google-signin-button type='standard' size='medium' width=\"320\" logo_alignment=\"center\" shape=\"pill\"></asl-google-signin-button>\r\n </div>\r\n\r\n <!-- Microsoft Login -->\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n <!-- Links -->\r\n <div class=\"links-container mb-5\">\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" color=\"primary\" (click)=\"recoverAccount()\">\r\n Forgot password?\r\n </a>\r\n\r\n <div *ngIf=\"appConfig.selfService\" class=\"signup-container\">\r\n <span>Don't have an account?</span>\r\n <a mat-button id=\"btnSignup\" color=\"primary\" (click)=\"signup()\">Sign up</a>\r\n </div>\r\n </div>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-footer>\r\n <div class=\"terms-container\">\r\n <mat-divider></mat-divider>\r\n <p class=\"terms-text\">\r\n By continuing, you acknowledge that you accept our\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openTerms()\">Terms and Conditions</a>\r\n and\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openPrivacy()\">Privacy Policy</a>.\r\n </p>\r\n </div>\r\n </mat-card-footer>\r\n </mat-card>\r\n</div>\r\n\r\n\r\n\r\n", styles: [".login-page{position:absolute;inset:0;overflow:auto}.background{min-height:100%}.container{display:flex;flex-direction:column;align-items:center;height:100vh}.logo{margin-top:3em;margin-bottom:1em}.container mat-card-header{text-align:center;justify-content:center}.container mat-card-title{text-align:center}.container mat-card-actions{display:flex;flex-direction:column;align-items:center;justify-content:center}.buttons{display:flex;flex-direction:row;justify-content:space-evenly}.button{display:flex;flex-direction:row;justify-content:center;margin-top:10px}.modern-login{min-height:100vh;display:flex;align-items:center;justify-content:center;background-color:#000}.login-card{width:100%;max-width:400px;padding:24px}.logo-container{text-align:center;margin-bottom:24px}.header-section{text-align:center;margin-bottom:32px}.login-title{margin:0 0 8px;font-size:32px;font-weight:300;letter-spacing:.5px;color:#000000de;text-align:center}.login-subtitle{margin:0;font-size:14px;font-weight:300;color:#0000008a;letter-spacing:.25px;text-align:center}.mat-card-header{text-align:center;justify-content:center;margin-bottom:24px}.mat-card-title{margin:0;font-size:24px;font-weight:400}.mat-card-subtitle{margin:8px 0 0}.login-form{display:flex;flex-direction:column}.button-container button{width:100%;margin-top:10px;border-radius:24px;height:40px}.divider{position:relative;text-align:center;margin:10px 0}.divider span{background:transparent;padding:0 16px;color:#666;font-size:14px;position:relative}.social-login{display:flex;justify-content:center}.modern-login .button button{width:100%;border-radius:24px;height:40px;margin-top:10px}.links-container{text-align:center;margin-top:16px}.signup-container{margin-top:16px;color:#0009}.signup-container span{margin-right:8px}.terms-container{margin-top:24px;padding:16px}.terms-text{color:#0009;font-size:12px;text-align:center;margin:16px 0 0;line-height:1.5}.terms-link{padding:0 4px;min-width:auto;line-height:inherit;height:auto}\n"], dependencies: [{ kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i5.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$1.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i6$1.MatCardContent, selector: "mat-card-content" }, { kind: "directive", type: i6$1.MatCardFooter, selector: "mat-card-footer" }, { kind: "component", type: i6$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i6$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i14.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: TextComponent, selector: "spa-text", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "format", "type", "width", "copyContent", "clearContent", "required", "min", "max", "regex", "suffix", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextMaskComponent, selector: "spa-text-mask", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "required", "min", "max", "regex", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "directive", type: i2$2.GoogleSigninButtonDirective, selector: "asl-google-signin-button", inputs: ["type", "size", "text", "shape", "theme", "logo_alignment", "width", "locale"] }] }); }
|
|
14661
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: LoginComponent, isStandalone: false, selector: "spa-login", ngImport: i0, template: "\r\n <div *ngIf=\"style=='default'\" class=\"login-page background tin-bg-login\">\r\n\r\n <div class=\"container\" >\r\n\r\n <div class=\"logo\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <mat-card class=\"mat-elevation-z3 \" style=\"width:400px; \">\r\n\r\n <mat-card-header style=\"margin-bottom: 30px;margin-top: 30px;\">\r\n <mat-card-title style=\"font-size: 40px;margin-bottom: 10px; margin-top: 20px; font-weight: 300\">{{appConfig.appName}}</mat-card-title>\r\n </mat-card-header>\r\n\r\n <mat-card-content *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n\r\n <div class=\"tin-input mt-2\">\r\n\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n </div>\r\n\r\n </mat-card-content>\r\n\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n\r\n <!-- Changed: Widths removed \u2014 all elements aligned to one column via .container CSS -->\r\n <div class=\"button\" *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n <button id=\"btnLogin\" mat-flat-button [disabled]=\"isProcessing\" (click)=\"login()\" color=\"primary\">Login</button>\r\n </div>\r\n\r\n <!-- Changed: Show signup button whenever selfService is enabled, not just for local/AD auth -->\r\n <div class=\"button\" *ngIf=\"appConfig.selfService\" >\r\n <button id=\"btnSignup\" mat-stroked-button color=\"primary\" (click)=\"signup()\">Create an account</button>\r\n </div>\r\n\r\n <div class=\"divider\" *ngIf=\"appConfig.googleAuth || appConfig.microsoftAuth\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Changed: Match modern Google button \u2014 pill shape + width aligned to the column -->\r\n <div class=\"button\" *ngIf=\"appConfig.googleAuth\">\r\n <asl-google-signin-button type='standard' width=\"350\" size='medium' shape=\"pill\" logo_alignment=\"center\" style=\"text-align: center;\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" style=\"margin-top: 1em\" (click)=\"recoverAccount()\">Forgot your password ?</a>\r\n\r\n\r\n\r\n\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"style=='modern'\" class=\"modern-login\">\r\n <mat-card class=\"login-card\">\r\n <!-- Logo -->\r\n <div class=\"logo-container\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <!-- Welcome text -->\r\n <div class=\"header-section\"> <!-- Changed: Use custom div instead of mat-card-header -->\r\n <h2 class=\"login-title\">{{appConfig.loginTitle ?? appConfig.appName}}</h2> <!-- Changed: Use h2 for title -->\r\n <p class=\"login-subtitle\" *ngIf=\"appConfig.loginMessage\">{{appConfig.loginMessage}}</p> <!-- Changed: Use p for subtitle -->\r\n </div>\r\n\r\n <mat-card-content>\r\n <div class=\"login-form\">\r\n <!-- Username -->\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" [appearance]=\"'outline'\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <!-- Password -->\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" [appearance]=\"'outline'\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px; margin-bottom: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n <!-- Login Button -->\r\n <div class=\"button-container\">\r\n <button id=\"btnLogin\" mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"login()\">\r\n Login\r\n </button>\r\n </div>\r\n\r\n <!-- Divider -->\r\n <div class=\"divider\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Social Login -->\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"social-login\">\r\n <asl-google-signin-button type='standard' size='medium' width=\"320\" logo_alignment=\"center\" shape=\"pill\"></asl-google-signin-button>\r\n </div>\r\n\r\n <!-- Microsoft Login -->\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n <!-- Links -->\r\n <div class=\"links-container mb-5\">\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" color=\"primary\" (click)=\"recoverAccount()\">\r\n Forgot password?\r\n </a>\r\n\r\n <div *ngIf=\"appConfig.selfService\" class=\"signup-container\">\r\n <span>Don't have an account?</span>\r\n <a mat-button id=\"btnSignup\" color=\"primary\" (click)=\"signup()\">Sign up</a>\r\n </div>\r\n </div>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-footer>\r\n <div class=\"terms-container\">\r\n <mat-divider></mat-divider>\r\n <p class=\"terms-text\">\r\n By continuing, you acknowledge that you accept our\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openTerms()\">Terms and Conditions</a>\r\n and\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openPrivacy()\">Privacy Policy</a>.\r\n </p>\r\n </div>\r\n </mat-card-footer>\r\n </mat-card>\r\n</div>\r\n\r\n\r\n\r\n", styles: [".login-page{position:absolute;inset:0;overflow:auto}.background{min-height:100%}.container{display:flex;flex-direction:column;align-items:center;height:100vh}.logo{margin-top:3em;margin-bottom:1em}.container mat-card-header{text-align:center;justify-content:center}.container mat-card-title{text-align:center}.container mat-card-actions{display:flex;flex-direction:column;align-items:center;justify-content:center}.buttons{display:flex;flex-direction:row;justify-content:space-evenly}.button{display:flex;flex-direction:row;justify-content:center;margin-top:10px}.container mat-card-content,.container mat-card-actions{width:350px;max-width:100%;box-sizing:border-box;padding-left:0;padding-right:0;margin-left:auto;margin-right:auto}.container mat-card-actions{align-items:stretch}.container .button{width:100%}.container .button button{width:100%!important}.container .tin-input ::ng-deep mat-form-field{width:100%!important;margin-right:0!important}.container .button asl-google-signin-button{display:flex;justify-content:center;width:100%}.container mat-card{padding-top:16px;padding-bottom:44px;border-radius:16px}.container mat-card-content{padding-top:0;padding-bottom:0}.container mat-card-actions .button:first-of-type,.container mat-card-actions .divider:first-of-type{margin-top:20px}.modern-login{min-height:100vh;display:flex;align-items:center;justify-content:center;background-color:#000}.login-card{width:100%;max-width:400px;padding:24px}.logo-container{text-align:center;margin-bottom:24px}.header-section{text-align:center;margin-bottom:32px}.login-title{margin:0 0 8px;font-size:32px;font-weight:300;letter-spacing:.5px;color:#000000de;text-align:center}.login-subtitle{margin:0;font-size:14px;font-weight:300;color:#0000008a;letter-spacing:.25px;text-align:center}.mat-card-header{text-align:center;justify-content:center;margin-bottom:24px}.mat-card-title{margin:0;font-size:24px;font-weight:400}.mat-card-subtitle{margin:8px 0 0}.login-form{display:flex;flex-direction:column}.button-container button{width:100%;margin-top:10px;border-radius:24px;height:40px}.divider{position:relative;text-align:center;margin:10px 0}.divider span{background:transparent;padding:0 16px;color:#666;font-size:14px;position:relative}.social-login{display:flex;justify-content:center}.modern-login .button button{width:100%;border-radius:24px;height:40px;margin-top:10px}.links-container{text-align:center;margin-top:16px}.signup-container{margin-top:16px;color:#0009}.signup-container span{margin-right:8px}.terms-container{margin-top:24px;padding:16px}.terms-text{color:#0009;font-size:12px;text-align:center;margin:16px 0 0;line-height:1.5}.terms-link{padding:0 4px;min-width:auto;line-height:inherit;height:auto}\n"], dependencies: [{ kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i5.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$1.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i6$1.MatCardContent, selector: "mat-card-content" }, { kind: "directive", type: i6$1.MatCardFooter, selector: "mat-card-footer" }, { kind: "component", type: i6$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i6$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i14.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: TextComponent, selector: "spa-text", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "format", "type", "width", "copyContent", "clearContent", "required", "min", "max", "regex", "suffix", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextMaskComponent, selector: "spa-text-mask", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "required", "min", "max", "regex", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "directive", type: i2$2.GoogleSigninButtonDirective, selector: "asl-google-signin-button", inputs: ["type", "size", "text", "shape", "theme", "logo_alignment", "width", "locale"] }] }); }
|
|
14556
14662
|
}
|
|
14557
14663
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LoginComponent, decorators: [{
|
|
14558
14664
|
type: Component,
|
|
14559
|
-
args: [{ selector: "spa-login", standalone: false, template: "\r\n <div *ngIf=\"style=='default'\" class=\"login-page background tin-bg-login\">\r\n\r\n <div class=\"container\" >\r\n\r\n <div class=\"logo\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <mat-card class=\"mat-elevation-z3 \" style=\"width:400px; \">\r\n\r\n <mat-card-header style=\"margin-bottom: 30px;margin-top: 30px;\">\r\n <mat-card-title style=\"font-size: 40px;margin-bottom: 10px; margin-top: 20px; font-weight: 300\">{{appConfig.appName}}</mat-card-title>\r\n </mat-card-header>\r\n\r\n <mat-card-content *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n\r\n <div class=\"tin-input mt-2\">\r\n\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed:
|
|
14665
|
+
args: [{ selector: "spa-login", standalone: false, template: "\r\n <div *ngIf=\"style=='default'\" class=\"login-page background tin-bg-login\">\r\n\r\n <div class=\"container\" >\r\n\r\n <div class=\"logo\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <mat-card class=\"mat-elevation-z3 \" style=\"width:400px; \">\r\n\r\n <mat-card-header style=\"margin-bottom: 30px;margin-top: 30px;\">\r\n <mat-card-title style=\"font-size: 40px;margin-bottom: 10px; margin-top: 20px; font-weight: 300\">{{appConfig.appName}}</mat-card-title>\r\n </mat-card-header>\r\n\r\n <mat-card-content *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n\r\n <div class=\"tin-input mt-2\">\r\n\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n </div>\r\n\r\n </mat-card-content>\r\n\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n\r\n <!-- Changed: Widths removed \u2014 all elements aligned to one column via .container CSS -->\r\n <div class=\"button\" *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n <button id=\"btnLogin\" mat-flat-button [disabled]=\"isProcessing\" (click)=\"login()\" color=\"primary\">Login</button>\r\n </div>\r\n\r\n <!-- Changed: Show signup button whenever selfService is enabled, not just for local/AD auth -->\r\n <div class=\"button\" *ngIf=\"appConfig.selfService\" >\r\n <button id=\"btnSignup\" mat-stroked-button color=\"primary\" (click)=\"signup()\">Create an account</button>\r\n </div>\r\n\r\n <div class=\"divider\" *ngIf=\"appConfig.googleAuth || appConfig.microsoftAuth\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Changed: Match modern Google button \u2014 pill shape + width aligned to the column -->\r\n <div class=\"button\" *ngIf=\"appConfig.googleAuth\">\r\n <asl-google-signin-button type='standard' width=\"350\" size='medium' shape=\"pill\" logo_alignment=\"center\" style=\"text-align: center;\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" style=\"margin-top: 1em\" (click)=\"recoverAccount()\">Forgot your password ?</a>\r\n\r\n\r\n\r\n\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"style=='modern'\" class=\"modern-login\">\r\n <mat-card class=\"login-card\">\r\n <!-- Logo -->\r\n <div class=\"logo-container\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <!-- Welcome text -->\r\n <div class=\"header-section\"> <!-- Changed: Use custom div instead of mat-card-header -->\r\n <h2 class=\"login-title\">{{appConfig.loginTitle ?? appConfig.appName}}</h2> <!-- Changed: Use h2 for title -->\r\n <p class=\"login-subtitle\" *ngIf=\"appConfig.loginMessage\">{{appConfig.loginMessage}}</p> <!-- Changed: Use p for subtitle -->\r\n </div>\r\n\r\n <mat-card-content>\r\n <div class=\"login-form\">\r\n <!-- Username -->\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" [appearance]=\"'outline'\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <!-- Password -->\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" [appearance]=\"'outline'\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px; margin-bottom: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n <!-- Login Button -->\r\n <div class=\"button-container\">\r\n <button id=\"btnLogin\" mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"login()\">\r\n Login\r\n </button>\r\n </div>\r\n\r\n <!-- Divider -->\r\n <div class=\"divider\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Social Login -->\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"social-login\">\r\n <asl-google-signin-button type='standard' size='medium' width=\"320\" logo_alignment=\"center\" shape=\"pill\"></asl-google-signin-button>\r\n </div>\r\n\r\n <!-- Microsoft Login -->\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n <!-- Links -->\r\n <div class=\"links-container mb-5\">\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" color=\"primary\" (click)=\"recoverAccount()\">\r\n Forgot password?\r\n </a>\r\n\r\n <div *ngIf=\"appConfig.selfService\" class=\"signup-container\">\r\n <span>Don't have an account?</span>\r\n <a mat-button id=\"btnSignup\" color=\"primary\" (click)=\"signup()\">Sign up</a>\r\n </div>\r\n </div>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-footer>\r\n <div class=\"terms-container\">\r\n <mat-divider></mat-divider>\r\n <p class=\"terms-text\">\r\n By continuing, you acknowledge that you accept our\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openTerms()\">Terms and Conditions</a>\r\n and\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openPrivacy()\">Privacy Policy</a>.\r\n </p>\r\n </div>\r\n </mat-card-footer>\r\n </mat-card>\r\n</div>\r\n\r\n\r\n\r\n", styles: [".login-page{position:absolute;inset:0;overflow:auto}.background{min-height:100%}.container{display:flex;flex-direction:column;align-items:center;height:100vh}.logo{margin-top:3em;margin-bottom:1em}.container mat-card-header{text-align:center;justify-content:center}.container mat-card-title{text-align:center}.container mat-card-actions{display:flex;flex-direction:column;align-items:center;justify-content:center}.buttons{display:flex;flex-direction:row;justify-content:space-evenly}.button{display:flex;flex-direction:row;justify-content:center;margin-top:10px}.container mat-card-content,.container mat-card-actions{width:350px;max-width:100%;box-sizing:border-box;padding-left:0;padding-right:0;margin-left:auto;margin-right:auto}.container mat-card-actions{align-items:stretch}.container .button{width:100%}.container .button button{width:100%!important}.container .tin-input ::ng-deep mat-form-field{width:100%!important;margin-right:0!important}.container .button asl-google-signin-button{display:flex;justify-content:center;width:100%}.container mat-card{padding-top:16px;padding-bottom:44px;border-radius:16px}.container mat-card-content{padding-top:0;padding-bottom:0}.container mat-card-actions .button:first-of-type,.container mat-card-actions .divider:first-of-type{margin-top:20px}.modern-login{min-height:100vh;display:flex;align-items:center;justify-content:center;background-color:#000}.login-card{width:100%;max-width:400px;padding:24px}.logo-container{text-align:center;margin-bottom:24px}.header-section{text-align:center;margin-bottom:32px}.login-title{margin:0 0 8px;font-size:32px;font-weight:300;letter-spacing:.5px;color:#000000de;text-align:center}.login-subtitle{margin:0;font-size:14px;font-weight:300;color:#0000008a;letter-spacing:.25px;text-align:center}.mat-card-header{text-align:center;justify-content:center;margin-bottom:24px}.mat-card-title{margin:0;font-size:24px;font-weight:400}.mat-card-subtitle{margin:8px 0 0}.login-form{display:flex;flex-direction:column}.button-container button{width:100%;margin-top:10px;border-radius:24px;height:40px}.divider{position:relative;text-align:center;margin:10px 0}.divider span{background:transparent;padding:0 16px;color:#666;font-size:14px;position:relative}.social-login{display:flex;justify-content:center}.modern-login .button button{width:100%;border-radius:24px;height:40px;margin-top:10px}.links-container{text-align:center;margin-top:16px}.signup-container{margin-top:16px;color:#0009}.signup-container span{margin-right:8px}.terms-container{margin-top:24px;padding:16px}.terms-text{color:#0009;font-size:12px;text-align:center;margin:16px 0 0;line-height:1.5}.terms-link{padding:0 4px;min-width:auto;line-height:inherit;height:auto}\n"] }]
|
|
14560
14666
|
}], ctorParameters: () => [{ type: HttpService }, { type: StorageService }, { type: i1$2.Router }, { type: MessageService }, { type: DataServiceLib }, { type: AuthService }, { type: LogService }, { type: i1$2.ActivatedRoute }, { type: NotificationsService }, { type: SignalRService }, { type: i10.MsalService }, { type: i1.MatDialog }] });
|
|
14561
14667
|
|
|
14562
14668
|
// Rewritten: Step-based signup flow for Google/MSAL OAuth users
|
|
@@ -21488,26 +21594,26 @@ class FixedAssetsDashboardComponent {
|
|
|
21488
21594
|
};
|
|
21489
21595
|
}
|
|
21490
21596
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetsDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
21491
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FixedAssetsDashboardComponent, isStandalone: false, selector: "spa-fixed-assets-dashboard", ngImport: i0, template: `
|
|
21492
|
-
<div class="dashboard-container">
|
|
21493
|
-
<h4 class="dashboard-title"><mat-icon>precision_manufacturing</mat-icon> Fixed Assets Dashboard</h4>
|
|
21494
|
-
<spa-tiles [config]="summaryTiles"></spa-tiles>
|
|
21495
|
-
<div style="margin-top: 16px;"></div>
|
|
21496
|
-
<spa-tiles [config]="chartTiles"></spa-tiles>
|
|
21497
|
-
<spa-charts [config]="chartConfig"></spa-charts>
|
|
21498
|
-
</div>
|
|
21597
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FixedAssetsDashboardComponent, isStandalone: false, selector: "spa-fixed-assets-dashboard", ngImport: i0, template: `
|
|
21598
|
+
<div class="dashboard-container">
|
|
21599
|
+
<h4 class="dashboard-title"><mat-icon>precision_manufacturing</mat-icon> Fixed Assets Dashboard</h4>
|
|
21600
|
+
<spa-tiles [config]="summaryTiles"></spa-tiles>
|
|
21601
|
+
<div style="margin-top: 16px;"></div>
|
|
21602
|
+
<spa-tiles [config]="chartTiles"></spa-tiles>
|
|
21603
|
+
<spa-charts [config]="chartConfig"></spa-charts>
|
|
21604
|
+
</div>
|
|
21499
21605
|
`, isInline: true, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}\n"], dependencies: [{ kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: ChartsComponent, selector: "spa-charts", inputs: ["config", "data", "reload"] }] }); }
|
|
21500
21606
|
}
|
|
21501
21607
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetsDashboardComponent, decorators: [{
|
|
21502
21608
|
type: Component,
|
|
21503
|
-
args: [{ selector: 'spa-fixed-assets-dashboard', template: `
|
|
21504
|
-
<div class="dashboard-container">
|
|
21505
|
-
<h4 class="dashboard-title"><mat-icon>precision_manufacturing</mat-icon> Fixed Assets Dashboard</h4>
|
|
21506
|
-
<spa-tiles [config]="summaryTiles"></spa-tiles>
|
|
21507
|
-
<div style="margin-top: 16px;"></div>
|
|
21508
|
-
<spa-tiles [config]="chartTiles"></spa-tiles>
|
|
21509
|
-
<spa-charts [config]="chartConfig"></spa-charts>
|
|
21510
|
-
</div>
|
|
21609
|
+
args: [{ selector: 'spa-fixed-assets-dashboard', template: `
|
|
21610
|
+
<div class="dashboard-container">
|
|
21611
|
+
<h4 class="dashboard-title"><mat-icon>precision_manufacturing</mat-icon> Fixed Assets Dashboard</h4>
|
|
21612
|
+
<spa-tiles [config]="summaryTiles"></spa-tiles>
|
|
21613
|
+
<div style="margin-top: 16px;"></div>
|
|
21614
|
+
<spa-tiles [config]="chartTiles"></spa-tiles>
|
|
21615
|
+
<spa-charts [config]="chartConfig"></spa-charts>
|
|
21616
|
+
</div>
|
|
21511
21617
|
`, standalone: false, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}\n"] }]
|
|
21512
21618
|
}] });
|
|
21513
21619
|
|
|
@@ -22060,28 +22166,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
22060
22166
|
// Config interfaces for the spa-landing component
|
|
22061
22167
|
// Consumer apps provide this config to drive the entire landing page
|
|
22062
22168
|
// Alsquare brand SVG — dark variant (for navbar on light backgrounds)
|
|
22063
|
-
const ALSQUARE_SVG_DARK = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
22064
|
-
<path d="M32 62C17.6 62 6 50.4 6 36S17.6 10 32 10c9.2 0 17.3 4.9 21.8 12.2" stroke="#1a3a7a" stroke-width="9" stroke-linecap="round" fill="none"/>
|
|
22065
|
-
<circle cx="32" cy="36" r="10" fill="white" stroke="#1a3a7a" stroke-width="7"/>
|
|
22066
|
-
<circle cx="32" cy="36" r="5.5" fill="#e8a020"/>
|
|
22067
|
-
<line x1="48" y1="22" x2="48" y2="62" stroke="#1a3a7a" stroke-width="9" stroke-linecap="round"/>
|
|
22068
|
-
<rect x="56" y="6" width="7" height="56" rx="3.5" fill="#e8a020"/>
|
|
22069
|
-
<rect x="67" y="0" width="6" height="6" rx="1" fill="#1a3a7a"/>
|
|
22070
|
-
<rect x="67" y="9" width="6" height="6" rx="1" fill="#1a3a7a"/>
|
|
22071
|
-
<rect x="75" y="0" width="5" height="5" rx="1" fill="#1a3a7a" opacity="0.7"/>
|
|
22072
|
-
<rect x="75" y="8" width="5" height="5" rx="1" fill="#1a3a7a" opacity="0.5"/>
|
|
22169
|
+
const ALSQUARE_SVG_DARK = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
22170
|
+
<path d="M32 62C17.6 62 6 50.4 6 36S17.6 10 32 10c9.2 0 17.3 4.9 21.8 12.2" stroke="#1a3a7a" stroke-width="9" stroke-linecap="round" fill="none"/>
|
|
22171
|
+
<circle cx="32" cy="36" r="10" fill="white" stroke="#1a3a7a" stroke-width="7"/>
|
|
22172
|
+
<circle cx="32" cy="36" r="5.5" fill="#e8a020"/>
|
|
22173
|
+
<line x1="48" y1="22" x2="48" y2="62" stroke="#1a3a7a" stroke-width="9" stroke-linecap="round"/>
|
|
22174
|
+
<rect x="56" y="6" width="7" height="56" rx="3.5" fill="#e8a020"/>
|
|
22175
|
+
<rect x="67" y="0" width="6" height="6" rx="1" fill="#1a3a7a"/>
|
|
22176
|
+
<rect x="67" y="9" width="6" height="6" rx="1" fill="#1a3a7a"/>
|
|
22177
|
+
<rect x="75" y="0" width="5" height="5" rx="1" fill="#1a3a7a" opacity="0.7"/>
|
|
22178
|
+
<rect x="75" y="8" width="5" height="5" rx="1" fill="#1a3a7a" opacity="0.5"/>
|
|
22073
22179
|
</svg>`;
|
|
22074
22180
|
// Alsquare brand SVG — white variant (for footer on dark backgrounds)
|
|
22075
|
-
const ALSQUARE_SVG_WHITE = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
22076
|
-
<path d="M32 62C17.6 62 6 50.4 6 36S17.6 10 32 10c9.2 0 17.3 4.9 21.8 12.2" stroke="#ffffff" stroke-width="9" stroke-linecap="round" fill="none"/>
|
|
22077
|
-
<circle cx="32" cy="36" r="10" fill="#0f172a" stroke="#ffffff" stroke-width="7"/>
|
|
22078
|
-
<circle cx="32" cy="36" r="5.5" fill="#e8a020"/>
|
|
22079
|
-
<line x1="48" y1="22" x2="48" y2="62" stroke="#ffffff" stroke-width="9" stroke-linecap="round"/>
|
|
22080
|
-
<rect x="56" y="6" width="7" height="56" rx="3.5" fill="#e8a020"/>
|
|
22081
|
-
<rect x="67" y="0" width="6" height="6" rx="1" fill="#ffffff"/>
|
|
22082
|
-
<rect x="67" y="9" width="6" height="6" rx="1" fill="#ffffff"/>
|
|
22083
|
-
<rect x="75" y="0" width="5" height="5" rx="1" fill="#ffffff" opacity="0.7"/>
|
|
22084
|
-
<rect x="75" y="8" width="5" height="5" rx="1" fill="#ffffff" opacity="0.5"/>
|
|
22181
|
+
const ALSQUARE_SVG_WHITE = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
22182
|
+
<path d="M32 62C17.6 62 6 50.4 6 36S17.6 10 32 10c9.2 0 17.3 4.9 21.8 12.2" stroke="#ffffff" stroke-width="9" stroke-linecap="round" fill="none"/>
|
|
22183
|
+
<circle cx="32" cy="36" r="10" fill="#0f172a" stroke="#ffffff" stroke-width="7"/>
|
|
22184
|
+
<circle cx="32" cy="36" r="5.5" fill="#e8a020"/>
|
|
22185
|
+
<line x1="48" y1="22" x2="48" y2="62" stroke="#ffffff" stroke-width="9" stroke-linecap="round"/>
|
|
22186
|
+
<rect x="56" y="6" width="7" height="56" rx="3.5" fill="#e8a020"/>
|
|
22187
|
+
<rect x="67" y="0" width="6" height="6" rx="1" fill="#ffffff"/>
|
|
22188
|
+
<rect x="67" y="9" width="6" height="6" rx="1" fill="#ffffff"/>
|
|
22189
|
+
<rect x="75" y="0" width="5" height="5" rx="1" fill="#ffffff" opacity="0.7"/>
|
|
22190
|
+
<rect x="75" y="8" width="5" height="5" rx="1" fill="#ffffff" opacity="0.5"/>
|
|
22085
22191
|
</svg>`;
|
|
22086
22192
|
|
|
22087
22193
|
/*
|
|
@@ -22093,5 +22199,5 @@ const ALSQUARE_SVG_WHITE = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://w
|
|
|
22093
22199
|
* Generated bundle index. Do not edit.
|
|
22094
22200
|
*/
|
|
22095
22201
|
|
|
22096
|
-
export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, AccountingModule, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AgentComponent, AgentService, AlertComponent, AlertConfig, AlertMessage, AnalyticsService, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AssetsService, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralModule, GeneralService, GradesComponent, GroupsComponent, HRModule, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryModule, InventoryService, InvitationsTableComponent, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansModule, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, ManufacturingModule, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, OverviewModule, PageComponent, PageConfig, PayrollDashboardComponent, PayrollModule, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchaseStatus, PurchasingDashboardComponent, PurchasingModule, PushNotificationService, ReceiptStatus, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SalesModule, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaHomeModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, TabService, TableComponent, TableConfig, TabsComponent, TasksComponent, TenancyModule, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, WorkflowModule, authGuard, dialogOptions, featureGuard, loginConfig, messageDialog, viewerDialog };
|
|
22202
|
+
export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, AccountingModule, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AgentComponent, AgentService, AlertComponent, AlertConfig, AlertMessage, AnalyticsService, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AssetsService, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralModule, GeneralService, GradesComponent, GroupsComponent, HRModule, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryModule, InventoryService, InvitationsTableComponent, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansModule, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, ManufacturingModule, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, OverviewModule, PageComponent, PageConfig, PayrollDashboardComponent, PayrollModule, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchaseStatus, PurchasingDashboardComponent, PurchasingModule, PushNotificationService, ReceiptStatus, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SalesModule, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaHomeModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, TIN_SPA_RUNTIME_CONFIG, TabService, TableComponent, TableConfig, TabsComponent, TasksComponent, TenancyModule, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, WorkflowModule, authGuard, dialogOptions, featureGuard, loginConfig, messageDialog, provideTinSpaRuntime, tinSpaLocationStrategyFactory, tinSpaMsalInstanceFactory, viewerDialog };
|
|
22097
22203
|
//# sourceMappingURL=tin-spa.mjs.map
|