@flyos/design-system 3.10.0 → 3.12.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.
@@ -47,7 +47,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
47
47
  // tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
48
48
  // Used only for the diagnostic message; the duplicate-instance detection itself is
49
49
  // version-agnostic, so a stale literal misnames a fork rather than hiding one.
50
- const FLY_DS_VERSION = '3.10.0';
50
+ const FLY_DS_VERSION = '3.12.0';
51
51
  const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
52
52
  /**
53
53
  * Records this design-system instance on the shared `scope` and returns the
@@ -3339,6 +3339,46 @@ function provideFlyStandaloneAuth(config) {
3339
3339
  ]);
3340
3340
  }
3341
3341
 
3342
+ const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
3343
+ /**
3344
+ * Auth wiring for a UI-developer build: sign in as a representative tenant user so the app runs
3345
+ * with **no STS**, and present a fixed bearer to whatever backend (real or mocked) it talks to.
3346
+ *
3347
+ * <p>The offline counterpart of `provideFlyStandaloneAuth`, and it deliberately provides the SAME
3348
+ * {@link FLY_STANDALONE_AUTH_CONFIG}. Two reasons, both learned the hard way:</p>
3349
+ * <ul>
3350
+ * <li>`flyStandaloneAuthInterceptor` injects that token unconditionally. Omitting it here does
3351
+ * not degrade gracefully — every HTTP call in the offline build throws a DI error at runtime,
3352
+ * which no typecheck or unit test catches.</li>
3353
+ * <li>The interceptor's path rules then hold identically in all build modes instead of being
3354
+ * re-stated per mode.</li>
3355
+ * </ul>
3356
+ *
3357
+ * <p>Seeding the shared {@link AuthService} store is the whole implementation, because that store
3358
+ * is the single source of truth for identity: `FlyStandaloneAuthService.isAuthenticated()` delegates
3359
+ * to it, so `flyStandaloneAuthGuard` passes and never reaches `startLogin()`, and the interceptor
3360
+ * reads the same token. No PKCE code path is entered offline.</p>
3361
+ *
3362
+ * <p><b>`STEP_UP_REAUTH_HANDLER` is intentionally NOT provided.</b> A step-up challenge cannot
3363
+ * arise offline, and a handler that redirected to a real STS would be actively wrong in a build
3364
+ * that has none.</p>
3365
+ *
3366
+ * @example
3367
+ * providers: [
3368
+ * offlineDataEnabled
3369
+ * ? provideFlyOfflineAuth({ auth: PPM_AUTH_CONFIG, user: OFFLINE_USER, accessToken: DEV_BEARER })
3370
+ * : provideFlyStandaloneAuth(PPM_AUTH_CONFIG),
3371
+ * ]
3372
+ */
3373
+ function provideFlyOfflineAuth(config) {
3374
+ return makeEnvironmentProviders([
3375
+ { provide: FLY_STANDALONE_AUTH_CONFIG, useValue: config.auth },
3376
+ provideAppInitializer(() => {
3377
+ inject(AuthService).setSession(config.user, config.accessToken, Date.now() + (config.sessionTtlMs ?? DEFAULT_TTL_MS));
3378
+ }),
3379
+ ]);
3380
+ }
3381
+
3342
3382
  /** Single source of truth for persisted / API theme strings. */
3343
3383
  const FLY_THEME_MODE_IDS = ['light', 'dark'];
3344
3384
  /** Factory default: `dark` (opaque dark / `html.dark-theme`); shell i18n `settings.theme.dark`. Keep in sync with `UserSettings` defaults. */
@@ -3569,8 +3609,23 @@ const FLY_REMOTE_BASE_PATH = new InjectionToken('FLY_REMOTE_BASE_PATH');
3569
3609
  * `FlyRemoteRouter.matchedRoute()`.
3570
3610
  */
3571
3611
  function matchFlyRoutePattern(pattern, segments) {
3612
+ const matched = matchFlyRoutePrefix(pattern, segments);
3613
+ return matched && matched.rest.length === 0 ? matched.params : null;
3614
+ }
3615
+ /**
3616
+ * Match a pattern against the FRONT of `segments`, returning what it captured
3617
+ * plus the segments it did not consume. `null` when the pattern cannot match.
3618
+ *
3619
+ * This is the prefix half of {@link matchFlyRoutePattern}, which is now simply
3620
+ * "prefix-match, and insist nothing is left over". A parent route stops at the
3621
+ * prefix and hands `rest` to its `children`.
3622
+ *
3623
+ * Exported for unit testing — consumers should rely on
3624
+ * `FlyRemoteRouter.matchedRoute()`.
3625
+ */
3626
+ function matchFlyRoutePrefix(pattern, segments) {
3572
3627
  const patternSegments = pattern.split('/').filter(Boolean);
3573
- if (patternSegments.length !== segments.length)
3628
+ if (patternSegments.length > segments.length)
3574
3629
  return null;
3575
3630
  const params = {};
3576
3631
  for (let i = 0; i < patternSegments.length; i++) {
@@ -3583,8 +3638,59 @@ function matchFlyRoutePattern(pattern, segments) {
3583
3638
  return null;
3584
3639
  }
3585
3640
  }
3586
- return params;
3641
+ return { params, rest: segments.slice(patternSegments.length) };
3642
+ }
3643
+ /**
3644
+ * Resolve `segments` against a route table, descending into `children`.
3645
+ *
3646
+ * First-match-wins in declaration order, at every level. A row WITHOUT children
3647
+ * must consume the URL exactly — the flat-table rule, unchanged. A row WITH
3648
+ * children consumes its own prefix and must then find a matching descendant;
3649
+ * if none matches, the row is skipped and its later siblings still get a turn,
3650
+ * so a routing miss never renders a layout wrapped around an empty outlet.
3651
+ *
3652
+ * `inherited` carries ancestor params down, which is what lets a child read a
3653
+ * `:id` captured by its layout.
3654
+ *
3655
+ * Exported for unit testing — consumers should rely on
3656
+ * `FlyRemoteRouter.matchedRoute()`.
3657
+ */
3658
+ function matchFlyRouteTable(routes, segments, inherited = {}) {
3659
+ for (const route of routes) {
3660
+ const prefix = matchFlyRoutePrefix(route.path, segments);
3661
+ if (!prefix)
3662
+ continue;
3663
+ const params = { ...inherited, ...prefix.params };
3664
+ if (!route.children?.length) {
3665
+ if (prefix.rest.length > 0)
3666
+ continue;
3667
+ return { route, params, child: null };
3668
+ }
3669
+ const child = matchFlyRouteTable(route.children, prefix.rest, params);
3670
+ if (child)
3671
+ return { route, params, child };
3672
+ }
3673
+ return null;
3587
3674
  }
3675
+ /** Walk a match chain to its leaf — the row that actually consumed the URL. */
3676
+ function deepestFlyMatch(match) {
3677
+ let current = match;
3678
+ while (current.child)
3679
+ current = current.child;
3680
+ return current;
3681
+ }
3682
+ /**
3683
+ * Nesting depth of a `<fly-remote-router-outlet>`, so an outlet knows which link
3684
+ * of the match chain it is responsible for. Each outlet provides its own depth
3685
+ * for its subtree, so the outlet a layout renders resolves to depth + 1 with no
3686
+ * wiring on the consumer's part — the same way Angular's own `RouterOutlet`
3687
+ * derives its level from the injector rather than an input.
3688
+ *
3689
+ * Consumers never provide this themselves.
3690
+ *
3691
+ * Available since design-system **3.12.0**.
3692
+ */
3693
+ const FLY_REMOTE_OUTLET_DEPTH = new InjectionToken('FLY_REMOTE_OUTLET_DEPTH');
3588
3694
 
3589
3695
  /**
3590
3696
  * FlyOS standard navigation surface for Business / Supporting App remotes.
@@ -3713,16 +3819,12 @@ class FlyRemoteRouter {
3713
3819
  *
3714
3820
  * Match order: routes are tried in declaration order. Put more specific
3715
3821
  * patterns (e.g. `'signals/:id'`) before catch-alls.
3822
+ *
3823
+ * On a table using `children` this is the ROOT of the match chain — follow
3824
+ * `.child` for the nested links, which is what the nested outlets do. A flat
3825
+ * table always yields `child: null`, so this reads exactly as it always did.
3716
3826
  */
3717
- matchedRoute = computed(() => {
3718
- const segs = this.segments();
3719
- for (const route of this.routes) {
3720
- const params = matchFlyRoutePattern(route.path, segs);
3721
- if (params != null)
3722
- return { route, params };
3723
- }
3724
- return null;
3725
- }, ...(ngDevMode ? [{ debugName: "matchedRoute" }] : /* istanbul ignore next */ []));
3827
+ matchedRoute = computed(() => matchFlyRouteTable(this.routes, this.segments()), ...(ngDevMode ? [{ debugName: "matchedRoute" }] : /* istanbul ignore next */ []));
3726
3828
  /**
3727
3829
  * Captured route params from the active route, e.g. `{ id: 'abc' }` for a
3728
3830
  * `'signals/:id'` match on `/signals/abc`. Empty object if no route matched
@@ -3734,7 +3836,13 @@ class FlyRemoteRouter {
3734
3836
  * readonly signalId = computed(() => this.flyRouter.params()['id'] ?? '');
3735
3837
  * ```
3736
3838
  */
3737
- params = computed(() => this.matchedRoute()?.params ?? {}, ...(ngDevMode ? [{ debugName: "params" }] : /* istanbul ignore next */ []));
3839
+ params = computed(() => {
3840
+ const match = this.matchedRoute();
3841
+ // The DEEPEST link, not the root: on a nested table the leaf is the row that
3842
+ // actually consumed the URL, and its params already carry everything its
3843
+ // ancestors captured. Identical to the root on a flat table.
3844
+ return match ? deepestFlyMatch(match).params : {};
3845
+ }, ...(ngDevMode ? [{ debugName: "params" }] : /* istanbul ignore next */ []));
3738
3846
  destroyRef = inject(DestroyRef);
3739
3847
  constructor() {
3740
3848
  if (!this.isEmbedded && this.router) {
@@ -5271,6 +5379,27 @@ function unwrapLoaded(loaded) {
5271
5379
  * - No query-param / hash handling — only path segments.
5272
5380
  * - No `RouterLink` directive — use `(click)="router.navigate(...)"`.
5273
5381
  *
5382
+ * ## Layout routes (3.12.0)
5383
+ *
5384
+ * A route with `children` renders its own component as a LAYOUT and resolves the
5385
+ * rest of the URL into a nested outlet placed in that layout's template:
5386
+ *
5387
+ * ```html
5388
+ * <!-- workspace-layout.component.html -->
5389
+ * <fly-entity-header [entity]="project()" />
5390
+ * <fly-tab-strip [projectId]="id()" />
5391
+ * <fly-remote-router-outlet /> <!-- the tab body, and only the tab body -->
5392
+ * ```
5393
+ *
5394
+ * The layout instance survives navigation between its children, because only the
5395
+ * nested outlet's component changes. That is what stops shared chrome from
5396
+ * re-fetching, re-animating and flashing empty on every tab switch — the failure
5397
+ * this feature exists to remove.
5398
+ *
5399
+ * Nesting is derived from the injector, not declared: each outlet publishes its
5400
+ * own depth to its subtree, so a nested outlet needs no input and no consumer
5401
+ * wiring. Depth beyond the end of the match chain renders nothing.
5402
+ *
5274
5403
  * Components rendered by this outlet read route params via `FlyRemoteRouter.params`:
5275
5404
  * ```ts
5276
5405
  * private readonly router = inject(FlyRemoteRouter);
@@ -5305,11 +5434,23 @@ function unwrapLoaded(loaded) {
5305
5434
  class FlyRemoteRouterOutletComponent {
5306
5435
  router = inject(FlyRemoteRouter);
5307
5436
  errorHandler = inject(ErrorHandler);
5437
+ /** This outlet's link in the match chain — 0 at the root, +1 per nesting. */
5438
+ depth = inject(FLY_REMOTE_OUTLET_DEPTH);
5308
5439
  /**
5309
- * Read directly from FlyRemoteRouter so the outlet re-renders whenever the
5310
- * URL changes (signal-based, OnPush-friendly).
5440
+ * The match this outlet is responsible for: `matchedRoute()` walked down
5441
+ * `depth` links. Read directly from FlyRemoteRouter so the outlet re-renders
5442
+ * whenever the URL changes (signal-based, OnPush-friendly).
5443
+ *
5444
+ * `null` past the end of the chain — a nested outlet in a layout whose match
5445
+ * has no child renders nothing rather than repeating its parent.
5311
5446
  */
5312
- matched = this.router.matchedRoute;
5447
+ matched = computed(() => {
5448
+ let match = this.router.matchedRoute();
5449
+ for (let level = 0; level < this.depth && match; level++) {
5450
+ match = match.child ?? null;
5451
+ }
5452
+ return match;
5453
+ }, ...(ngDevMode ? [{ debugName: "matched" }] : /* istanbul ignore next */ []));
5313
5454
  /** Loaders that have resolved — consulted synchronously, so a revisit never flashes. */
5314
5455
  loaded = new Map();
5315
5456
  /** Loaders currently in flight, so a param change mid-load doesn't start a second fetch. */
@@ -5363,10 +5504,19 @@ class FlyRemoteRouterOutletComponent {
5363
5504
  });
5364
5505
  }
5365
5506
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyRemoteRouterOutletComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5366
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyRemoteRouterOutletComponent, isStandalone: true, selector: "fly-remote-router-outlet", ngImport: i0, template: `
5367
- @if (rendered(); as cmp) {
5368
- <ng-container *ngComponentOutlet="cmp" />
5369
- }
5507
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyRemoteRouterOutletComponent, isStandalone: true, selector: "fly-remote-router-outlet", providers: [
5508
+ {
5509
+ // Each outlet publishes its own depth to its subtree, so an outlet a
5510
+ // layout renders resolves one level deeper with nothing declared by the
5511
+ // consumer. `skipSelf` reads the ANCESTOR outlet's value; absent (the
5512
+ // top-level outlet) it starts the chain at 0.
5513
+ provide: FLY_REMOTE_OUTLET_DEPTH,
5514
+ useFactory: () => (inject(FLY_REMOTE_OUTLET_DEPTH, { optional: true, skipSelf: true }) ?? -1) + 1,
5515
+ },
5516
+ ], ngImport: i0, template: `
5517
+ @if (rendered(); as cmp) {
5518
+ <ng-container *ngComponentOutlet="cmp" />
5519
+ }
5370
5520
  `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5371
5521
  }
5372
5522
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyRemoteRouterOutletComponent, decorators: [{
@@ -5376,10 +5526,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
5376
5526
  standalone: true,
5377
5527
  imports: [NgComponentOutlet],
5378
5528
  changeDetection: ChangeDetectionStrategy.OnPush,
5379
- template: `
5380
- @if (rendered(); as cmp) {
5381
- <ng-container *ngComponentOutlet="cmp" />
5382
- }
5529
+ providers: [
5530
+ {
5531
+ // Each outlet publishes its own depth to its subtree, so an outlet a
5532
+ // layout renders resolves one level deeper with nothing declared by the
5533
+ // consumer. `skipSelf` reads the ANCESTOR outlet's value; absent (the
5534
+ // top-level outlet) it starts the chain at 0.
5535
+ provide: FLY_REMOTE_OUTLET_DEPTH,
5536
+ useFactory: () => (inject(FLY_REMOTE_OUTLET_DEPTH, { optional: true, skipSelf: true }) ?? -1) + 1,
5537
+ },
5538
+ ],
5539
+ template: `
5540
+ @if (rendered(); as cmp) {
5541
+ <ng-container *ngComponentOutlet="cmp" />
5542
+ }
5383
5543
  `,
5384
5544
  }]
5385
5545
  }], ctorParameters: () => [] });
@@ -9829,11 +9989,11 @@ class FlyBlockUiComponent {
9829
9989
  return k && k.length > 0 ? k : 'common.loading';
9830
9990
  }, ...(ngDevMode ? [{ debugName: "resolvedMessageKey" }] : /* istanbul ignore next */ []));
9831
9991
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBlockUiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9832
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyBlockUiComponent, isStandalone: true, selector: "fly-block-ui", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (active()) {\r\n <div\r\n class=\"fly-block-ui\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n aria-busy=\"true\"\r\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\r\n <div class=\"fly-block-ui__card\">\r\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\r\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9992
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyBlockUiComponent, isStandalone: true, selector: "fly-block-ui", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9833
9993
  }
9834
9994
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBlockUiComponent, decorators: [{
9835
9995
  type: Component,
9836
- args: [{ selector: 'fly-block-ui', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (active()) {\r\n <div\r\n class=\"fly-block-ui\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n aria-busy=\"true\"\r\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\r\n <div class=\"fly-block-ui__card\">\r\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\r\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"] }]
9996
+ args: [{ selector: 'fly-block-ui', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"] }]
9837
9997
  }], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: true }] }], messageKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageKey", required: false }] }] } });
9838
9998
 
9839
9999
  const STATE_KEY = {
@@ -20134,11 +20294,11 @@ class FlyPeoplePickerComponent {
20134
20294
  this.selectionChange.emit(this._selected().map((o) => o.id));
20135
20295
  }
20136
20296
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPeoplePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20137
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyPeoplePickerComponent, isStandalone: true, selector: "fly-people-picker", inputs: { searchFn: { classPropertyName: "searchFn", publicName: "searchFn", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, excludeIds: { classPropertyName: "excludeIds", publicName: "excludeIds", isSignal: true, isRequired: false, transformFunction: null }, initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { classAttribute: "fly-people-picker" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: FlyTypeaheadComponent, descendants: true }], ngImport: i0, template: "@if (selected().length > 0) {\r\n <ul class=\"fly-people-picker__chips\" role=\"list\">\r\n @for (o of selected(); track o.id) {\r\n <li class=\"fly-people-picker__chip\" role=\"listitem\">\r\n <span class=\"fly-people-picker__chip-avatar\" aria-hidden=\"true\">\r\n @if (o.avatarUrl) {\r\n <img [src]=\"o.avatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-people-picker__chip-initials\">{{ initialsFor(o.displayName) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-people-picker__chip-name\" [title]=\"o.email || o.displayName\">{{ o.displayName }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-people-picker__chip-remove\"\r\n [attr.aria-label]=\"'people_picker.remove' | translate: { name: o.displayName }\"\r\n (click)=\"remove(o.id)\">\r\n \u2715\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n}\r\n\r\n@if (showSearch()) {\r\n <fly-typeahead\r\n [searchFn]=\"typeaheadSearchFn\"\r\n [placeholder]=\"placeholder() || ('people_picker.search_placeholder' | translate)\"\r\n [allowFreeText]=\"false\"\r\n (selected)=\"onPicked($event)\" />\r\n} @else {\r\n <button type=\"button\" class=\"fly-people-picker__change-btn\" (click)=\"change()\">\r\n {{ 'people_picker.change' | translate }}\r\n </button>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 999px;display:flex;flex-direction:column;gap:8px;inline-size:100%;color:var(--_text);font-size:13px}.fly-people-picker__chips{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.fly-people-picker__chip{display:inline-flex;align-items:center;gap:6px;padding-block:3px;padding-inline:3px 6px;border-radius:var(--_radius);background:var(--_fill);max-inline-size:220px}.fly-people-picker__chip-avatar{inline-size:22px;block-size:22px;border-radius:50%;overflow:hidden;flex:none;background:var(--_surface);display:flex;align-items:center;justify-content:center}.fly-people-picker__chip-avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-people-picker__chip-initials{font-size:9px;font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-people-picker__chip-name{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-people-picker__chip-remove{border:none;background:transparent;padding:2px;margin-inline-start:2px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-people-picker__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-people-picker__change-btn{align-self:flex-start;border:1px solid var(--_border);border-radius:8px;background:var(--_surface);padding:5px 12px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-people-picker__change-btn:hover{background:var(--_surface-hover)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FlyTypeaheadComponent, selector: "fly-typeahead", inputs: ["searchFn", "debounceMs", "placeholder", "ariaLabel", "value", "allowFreeText", "openOnFocus"], outputs: ["valueChange", "selected", "cleared"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20297
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyPeoplePickerComponent, isStandalone: true, selector: "fly-people-picker", inputs: { searchFn: { classPropertyName: "searchFn", publicName: "searchFn", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, excludeIds: { classPropertyName: "excludeIds", publicName: "excludeIds", isSignal: true, isRequired: false, transformFunction: null }, initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { classAttribute: "fly-people-picker" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: FlyTypeaheadComponent, descendants: true }], ngImport: i0, template: "@if (selected().length > 0) {\n <ul class=\"fly-people-picker__chips\" role=\"list\">\n @for (o of selected(); track o.id) {\n <li class=\"fly-people-picker__chip\" role=\"listitem\">\n <span class=\"fly-people-picker__chip-avatar\" aria-hidden=\"true\">\n @if (o.avatarUrl) {\n <img [src]=\"o.avatarUrl\" alt=\"\" />\n } @else {\n <span class=\"fly-people-picker__chip-initials\">{{ initialsFor(o.displayName) }}</span>\n }\n </span>\n <span class=\"fly-people-picker__chip-name\" [title]=\"o.email || o.displayName\">{{ o.displayName }}</span>\n <button\n type=\"button\"\n class=\"fly-people-picker__chip-remove\"\n [attr.aria-label]=\"'people_picker.remove' | translate: { name: o.displayName }\"\n (click)=\"remove(o.id)\">\n \u2715\n </button>\n </li>\n }\n </ul>\n}\n\n@if (showSearch()) {\n <fly-typeahead\n [searchFn]=\"typeaheadSearchFn\"\n [placeholder]=\"placeholder() || ('people_picker.search_placeholder' | translate)\"\n [allowFreeText]=\"false\"\n (selected)=\"onPicked($event)\" />\n} @else {\n <button type=\"button\" class=\"fly-people-picker__change-btn\" (click)=\"change()\">\n {{ 'people_picker.change' | translate }}\n </button>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 999px;display:flex;flex-direction:column;gap:8px;inline-size:100%;color:var(--_text);font-size:13px}.fly-people-picker__chips{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.fly-people-picker__chip{display:inline-flex;align-items:center;gap:6px;padding-block:3px;padding-inline:3px 6px;border-radius:var(--_radius);background:var(--_fill);max-inline-size:220px}.fly-people-picker__chip-avatar{inline-size:22px;block-size:22px;border-radius:50%;overflow:hidden;flex:none;background:var(--_surface);display:flex;align-items:center;justify-content:center}.fly-people-picker__chip-avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-people-picker__chip-initials{font-size:9px;font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-people-picker__chip-name{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-people-picker__chip-remove{border:none;background:transparent;padding:2px;margin-inline-start:2px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-people-picker__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-people-picker__change-btn{align-self:flex-start;border:1px solid var(--_border);border-radius:8px;background:var(--_surface);padding:5px 12px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-people-picker__change-btn:hover{background:var(--_surface-hover)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FlyTypeaheadComponent, selector: "fly-typeahead", inputs: ["searchFn", "debounceMs", "placeholder", "ariaLabel", "value", "allowFreeText", "openOnFocus"], outputs: ["valueChange", "selected", "cleared"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20138
20298
  }
20139
20299
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPeoplePickerComponent, decorators: [{
20140
20300
  type: Component,
20141
- args: [{ selector: 'fly-people-picker', standalone: true, imports: [CommonModule, TranslatePipe, FlyTypeaheadComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-people-picker' }, template: "@if (selected().length > 0) {\r\n <ul class=\"fly-people-picker__chips\" role=\"list\">\r\n @for (o of selected(); track o.id) {\r\n <li class=\"fly-people-picker__chip\" role=\"listitem\">\r\n <span class=\"fly-people-picker__chip-avatar\" aria-hidden=\"true\">\r\n @if (o.avatarUrl) {\r\n <img [src]=\"o.avatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-people-picker__chip-initials\">{{ initialsFor(o.displayName) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-people-picker__chip-name\" [title]=\"o.email || o.displayName\">{{ o.displayName }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-people-picker__chip-remove\"\r\n [attr.aria-label]=\"'people_picker.remove' | translate: { name: o.displayName }\"\r\n (click)=\"remove(o.id)\">\r\n \u2715\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n}\r\n\r\n@if (showSearch()) {\r\n <fly-typeahead\r\n [searchFn]=\"typeaheadSearchFn\"\r\n [placeholder]=\"placeholder() || ('people_picker.search_placeholder' | translate)\"\r\n [allowFreeText]=\"false\"\r\n (selected)=\"onPicked($event)\" />\r\n} @else {\r\n <button type=\"button\" class=\"fly-people-picker__change-btn\" (click)=\"change()\">\r\n {{ 'people_picker.change' | translate }}\r\n </button>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 999px;display:flex;flex-direction:column;gap:8px;inline-size:100%;color:var(--_text);font-size:13px}.fly-people-picker__chips{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.fly-people-picker__chip{display:inline-flex;align-items:center;gap:6px;padding-block:3px;padding-inline:3px 6px;border-radius:var(--_radius);background:var(--_fill);max-inline-size:220px}.fly-people-picker__chip-avatar{inline-size:22px;block-size:22px;border-radius:50%;overflow:hidden;flex:none;background:var(--_surface);display:flex;align-items:center;justify-content:center}.fly-people-picker__chip-avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-people-picker__chip-initials{font-size:9px;font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-people-picker__chip-name{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-people-picker__chip-remove{border:none;background:transparent;padding:2px;margin-inline-start:2px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-people-picker__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-people-picker__change-btn{align-self:flex-start;border:1px solid var(--_border);border-radius:8px;background:var(--_surface);padding:5px 12px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-people-picker__change-btn:hover{background:var(--_surface-hover)}\n"] }]
20301
+ args: [{ selector: 'fly-people-picker', standalone: true, imports: [CommonModule, TranslatePipe, FlyTypeaheadComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-people-picker' }, template: "@if (selected().length > 0) {\n <ul class=\"fly-people-picker__chips\" role=\"list\">\n @for (o of selected(); track o.id) {\n <li class=\"fly-people-picker__chip\" role=\"listitem\">\n <span class=\"fly-people-picker__chip-avatar\" aria-hidden=\"true\">\n @if (o.avatarUrl) {\n <img [src]=\"o.avatarUrl\" alt=\"\" />\n } @else {\n <span class=\"fly-people-picker__chip-initials\">{{ initialsFor(o.displayName) }}</span>\n }\n </span>\n <span class=\"fly-people-picker__chip-name\" [title]=\"o.email || o.displayName\">{{ o.displayName }}</span>\n <button\n type=\"button\"\n class=\"fly-people-picker__chip-remove\"\n [attr.aria-label]=\"'people_picker.remove' | translate: { name: o.displayName }\"\n (click)=\"remove(o.id)\">\n \u2715\n </button>\n </li>\n }\n </ul>\n}\n\n@if (showSearch()) {\n <fly-typeahead\n [searchFn]=\"typeaheadSearchFn\"\n [placeholder]=\"placeholder() || ('people_picker.search_placeholder' | translate)\"\n [allowFreeText]=\"false\"\n (selected)=\"onPicked($event)\" />\n} @else {\n <button type=\"button\" class=\"fly-people-picker__change-btn\" (click)=\"change()\">\n {{ 'people_picker.change' | translate }}\n </button>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 999px;display:flex;flex-direction:column;gap:8px;inline-size:100%;color:var(--_text);font-size:13px}.fly-people-picker__chips{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.fly-people-picker__chip{display:inline-flex;align-items:center;gap:6px;padding-block:3px;padding-inline:3px 6px;border-radius:var(--_radius);background:var(--_fill);max-inline-size:220px}.fly-people-picker__chip-avatar{inline-size:22px;block-size:22px;border-radius:50%;overflow:hidden;flex:none;background:var(--_surface);display:flex;align-items:center;justify-content:center}.fly-people-picker__chip-avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-people-picker__chip-initials{font-size:9px;font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-people-picker__chip-name{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-people-picker__chip-remove{border:none;background:transparent;padding:2px;margin-inline-start:2px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-people-picker__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-people-picker__change-btn{align-self:flex-start;border:1px solid var(--_border);border-radius:8px;background:var(--_surface);padding:5px 12px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-people-picker__change-btn:hover{background:var(--_surface-hover)}\n"] }]
20142
20302
  }], ctorParameters: () => [], propDecorators: { searchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchFn", required: true }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], excludeIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludeIds", required: false }] }], initialSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelection", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], typeahead: [{
20143
20303
  type: ViewChild,
20144
20304
  args: [FlyTypeaheadComponent]
@@ -23103,6 +23263,298 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
23103
23263
  args: [{ selector: 'fly-meta', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<dl>\r\n @for (item of items(); track $index) {\r\n <div>\r\n <dt>{{ item.labelKey | translate }}</dt>\r\n <dd [class.mono]=\"item.mono ?? false\">{{ item.value }}</dd>\r\n </div>\r\n }\r\n <ng-content />\r\n</dl>\r\n", styles: [":host{display:block}dl{margin:0;display:flex;flex-direction:column;gap:10px}dl ::ng-deep>div{display:grid;grid-template-columns:96px 1fr;gap:var(--sp-3);align-items:baseline;font-size:var(--text-sm)}dl ::ng-deep dt{margin:0;font-size:10.5px;font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4)}dl ::ng-deep dd{margin:0;min-width:0;color:var(--ink);word-break:break-word;line-height:1.45}dl ::ng-deep dd.mono,dl ::ng-deep dd .mono{font-size:var(--text-2xs)}\n"] }]
23104
23264
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }] } });
23105
23265
 
23266
+ /**
23267
+ * Generic Overview-section content surface — the `--w03`/`--w06` tinted
23268
+ * plate a non-card section body (Description text, a chips tray, a members
23269
+ * tray, an activity tray) sits on inside a {@link FlyOverviewSectionComponent}.
23270
+ * Deliberately presentation-only: no shadow, gradient, hover, or cursor —
23271
+ * this is quiet inner paper, not a clickable surface.
23272
+ *
23273
+ * Content is left to projection so each Overview variant keeps its own real
23274
+ * markup (and, for Members/Chips/Activity, its own live Angular bindings and
23275
+ * interactions) — this component only paints the box.
23276
+ *
23277
+ * `[proseText]` additionally applies the Description-section body-copy
23278
+ * typography directly on the host, for the common case of one bound string
23279
+ * with nothing else inside. Leave it off for any other content (chips,
23280
+ * member rows, a nested `fly-overview-rows`) — those bring their own type.
23281
+ *
23282
+ * ```html
23283
+ * <fly-overview-surface [proseText]="true">{{ project().description }}</fly-overview-surface>
23284
+ *
23285
+ * <fly-overview-surface>
23286
+ * @for (m of project().members; track m.id) { <fly-chip>{{ m.name }}</fly-chip> }
23287
+ * </fly-overview-surface>
23288
+ * ```
23289
+ */
23290
+ class FlyOverviewSurfaceComponent {
23291
+ /** Apply the Description-section body-copy typography on the host. */
23292
+ proseText = input(false, ...(ngDevMode ? [{ debugName: "proseText" }] : /* istanbul ignore next */ []));
23293
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyOverviewSurfaceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23294
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.19", type: FlyOverviewSurfaceComponent, isStandalone: true, selector: "fly-overview-surface", inputs: { proseText: { classPropertyName: "proseText", publicName: "proseText", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.ovsf--prose": "proseText()" }, classAttribute: "ovsf" }, ngImport: i0, template: '<ng-content />', isInline: true, styles: [":host{display:block;padding:12px 14px;border-radius:11px;background:var(--w03);border:1px solid var(--w06)}:host(.ovsf--prose){font-family:var(--font-family);font-weight:400;font-size:13px;line-height:1.55;color:var(--w72);text-wrap:pretty}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23295
+ }
23296
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyOverviewSurfaceComponent, decorators: [{
23297
+ type: Component,
23298
+ args: [{ selector: 'fly-overview-surface', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
23299
+ class: 'ovsf',
23300
+ '[class.ovsf--prose]': 'proseText()',
23301
+ }, template: '<ng-content />', styles: [":host{display:block;padding:12px 14px;border-radius:11px;background:var(--w03);border:1px solid var(--w06)}:host(.ovsf--prose){font-family:var(--font-family);font-weight:400;font-size:13px;line-height:1.55;color:var(--w72);text-wrap:pretty}\n"] }]
23302
+ }], propDecorators: { proseText: [{ type: i0.Input, args: [{ isSignal: true, alias: "proseText", required: false }] }] } });
23303
+
23304
+ /**
23305
+ * Overview "Summary" KPI row — a wrapping row of flat stat cards, each with
23306
+ * the value/label stack at inline-start and the icon on a solid tone tile at
23307
+ * inline-end (the KPI-tile treatment, 2026-08-20 fidelity pass).
23308
+ * `order: 1`/`order: 2` (not `flex-direction: row-reverse`) drive that split
23309
+ * so the layout auto-mirrors under `dir="rtl"`: inline-start/-end follow
23310
+ * direction, a fixed left/right would not.
23311
+ *
23312
+ * Default layout is a wrapping flex row (any item count degrades gracefully);
23313
+ * a design that pins an exact no-reflow column count (the Project-Details
23314
+ * Overview pins five) passes `columns` instead.
23315
+ *
23316
+ * ```html
23317
+ * <fly-overview-kpi-row [columns]="5" [items]="[
23318
+ * { icon: 'pi-chart-line', value: '64%', labelKey: 'ppm.projects.overview.progress', tone: 'blue' },
23319
+ * { icon: 'pi-flag', value: '3/8', labelKey: 'ppm.projects.overview.milestones', tone: 'purple' },
23320
+ * ]" />
23321
+ * ```
23322
+ */
23323
+ class FlyOverviewKpiRowComponent {
23324
+ items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
23325
+ /** Exact equal-width column count (no reflow). Omit for the default wrapping flex row. */
23326
+ columns = input(...(ngDevMode ? [undefined, { debugName: "columns" }] : /* istanbul ignore next */ []));
23327
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyOverviewKpiRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23328
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyOverviewKpiRowComponent, isStandalone: true, selector: "fly-overview-kpi-row", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
23329
+ <div class="okr" [class.okr--grid]="columns() != null" [style.--okr-columns]="columns()">
23330
+ @for (item of items(); track item.labelKey) {
23331
+ <div class="okr__card" [attr.data-tone]="item.tone">
23332
+ <div class="okr__text">
23333
+ <span class="okr__value">{{ item.value }}</span>
23334
+ <span class="okr__label">{{ item.labelKey | translate }}</span>
23335
+ </div>
23336
+ <i class="pi {{ item.icon }} okr__icon" aria-hidden="true"></i>
23337
+ </div>
23338
+ }
23339
+ </div>
23340
+ `, isInline: true, styles: [":host{display:block}.okr{display:flex;align-items:stretch;gap:10px;flex-wrap:wrap}.okr--grid{--okr-columns: 1;display:grid;grid-template-columns:repeat(var(--okr-columns),minmax(0,1fr))}.okr__card{flex:1;min-width:150px;display:flex;align-items:center;gap:11px;padding:13px 14px;border-radius:var(--r-lg);background:var(--w03);border:1px solid var(--w06)}.okr__card[data-tone=blue]{--okr-tone: var(--sys-blue)}.okr__card[data-tone=purple]{--okr-tone: var(--sys-purple)}.okr__card[data-tone=orange]{--okr-tone: var(--sys-orange)}.okr__card[data-tone=red]{--okr-tone: var(--sys-red)}.okr__card[data-tone=teal]{--okr-tone: var(--sys-teal)}.okr__text{order:1;flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.okr__icon{order:2;flex:none;inline-size:32px;block-size:32px;border-radius:9px;display:grid;place-items:center;background:var(--okr-tone, var(--w6));color:var(--ink-inverse);font-size:15px}.okr__value{font-family:var(--font-family-display);font-weight:var(--fw-bold);font-size:20px;line-height:1;letter-spacing:var(--tracking-tight);font-variant-numeric:tabular-nums;color:var(--ink)}.okr__label{font-family:var(--font-family);font-weight:var(--fw-medium);font-size:var(--text-2xs);line-height:1.2;color:var(--w45)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23341
+ }
23342
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyOverviewKpiRowComponent, decorators: [{
23343
+ type: Component,
23344
+ args: [{ selector: 'fly-overview-kpi-row', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
23345
+ <div class="okr" [class.okr--grid]="columns() != null" [style.--okr-columns]="columns()">
23346
+ @for (item of items(); track item.labelKey) {
23347
+ <div class="okr__card" [attr.data-tone]="item.tone">
23348
+ <div class="okr__text">
23349
+ <span class="okr__value">{{ item.value }}</span>
23350
+ <span class="okr__label">{{ item.labelKey | translate }}</span>
23351
+ </div>
23352
+ <i class="pi {{ item.icon }} okr__icon" aria-hidden="true"></i>
23353
+ </div>
23354
+ }
23355
+ </div>
23356
+ `, styles: [":host{display:block}.okr{display:flex;align-items:stretch;gap:10px;flex-wrap:wrap}.okr--grid{--okr-columns: 1;display:grid;grid-template-columns:repeat(var(--okr-columns),minmax(0,1fr))}.okr__card{flex:1;min-width:150px;display:flex;align-items:center;gap:11px;padding:13px 14px;border-radius:var(--r-lg);background:var(--w03);border:1px solid var(--w06)}.okr__card[data-tone=blue]{--okr-tone: var(--sys-blue)}.okr__card[data-tone=purple]{--okr-tone: var(--sys-purple)}.okr__card[data-tone=orange]{--okr-tone: var(--sys-orange)}.okr__card[data-tone=red]{--okr-tone: var(--sys-red)}.okr__card[data-tone=teal]{--okr-tone: var(--sys-teal)}.okr__text{order:1;flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.okr__icon{order:2;flex:none;inline-size:32px;block-size:32px;border-radius:9px;display:grid;place-items:center;background:var(--okr-tone, var(--w6));color:var(--ink-inverse);font-size:15px}.okr__value{font-family:var(--font-family-display);font-weight:var(--fw-bold);font-size:20px;line-height:1;letter-spacing:var(--tracking-tight);font-variant-numeric:tabular-nums;color:var(--ink)}.okr__label{font-family:var(--font-family);font-weight:var(--fw-medium);font-size:var(--text-2xs);line-height:1.2;color:var(--w45)}\n"] }]
23357
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }] } });
23358
+
23359
+ /**
23360
+ * Overview "Details" body — a self-contained `--w03`/`--w06` surface holding
23361
+ * label/value rows. Distinct from {@link FlyMetaListComponent} (`fly-meta`):
23362
+ * that component is the compact sidecard metalist (96px label column,
23363
+ * uppercase eyebrow labels, no surrounding surface — it expects to sit
23364
+ * inside a `fly-detail-card`), while this one is the wider Overview-page
23365
+ * "Details" block (132px label column, sentence-case labels, and the surface
23366
+ * built in) — the two visual specs don't share numbers, so this stays its
23367
+ * own component rather than a variant of the sidecard one.
23368
+ *
23369
+ * Simple rows come from `rows`; a richer value (a link, a chip, a user
23370
+ * label) projects through the default slot — reuse the `ovr__row` /
23371
+ * `ovr__label` / `ovr__value` classes on the projected markup to pick up the
23372
+ * same layout.
23373
+ *
23374
+ * ```html
23375
+ * <fly-overview-rows [rows]="[
23376
+ * { labelKey: 'ppm.projects.overview.owner', value: project().ownerName },
23377
+ * { labelKey: 'ppm.projects.overview.dueDate', value: project().dueDateLabel },
23378
+ * ]">
23379
+ * <div class="ovr__row">
23380
+ * <span class="ovr__label">{{ 'ppm.projects.overview.strategy' | translate }}</span>
23381
+ * <a class="ovr__value" [routerLink]="['/strategies', project().strategyId]">{{ project().strategyName }}</a>
23382
+ * </div>
23383
+ * </fly-overview-rows>
23384
+ * ```
23385
+ */
23386
+ class FlyOverviewRowsComponent {
23387
+ rows = input([], ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
23388
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyOverviewRowsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23389
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyOverviewRowsComponent, isStandalone: true, selector: "fly-overview-rows", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
23390
+ <div class="ovr">
23391
+ @for (row of rows(); track row.labelKey) {
23392
+ <div class="ovr__row">
23393
+ <span class="ovr__label">{{ row.labelKey | translate }}</span>
23394
+ <span class="ovr__value">{{ row.value }}</span>
23395
+ </div>
23396
+ }
23397
+ <ng-content />
23398
+ </div>
23399
+ `, isInline: true, styles: [":host{display:block}.ovr{display:flex;flex-direction:column;gap:9px;padding:12px 14px;border-radius:11px;background:var(--w03);border:1px solid var(--w06)}.ovr ::ng-deep .ovr__row{display:flex;align-items:baseline;gap:14px}.ovr ::ng-deep .ovr__label{flex:0 0 auto;width:132px;font-family:var(--font-family);font-weight:var(--fw-medium);font-size:12px;line-height:1.4;color:var(--w45)}.ovr ::ng-deep .ovr__value{flex:1;min-width:0;font-family:var(--font-family);font-weight:var(--fw-medium);font-size:var(--text-sm);line-height:1.4;color:var(--w85)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23400
+ }
23401
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyOverviewRowsComponent, decorators: [{
23402
+ type: Component,
23403
+ args: [{ selector: 'fly-overview-rows', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
23404
+ <div class="ovr">
23405
+ @for (row of rows(); track row.labelKey) {
23406
+ <div class="ovr__row">
23407
+ <span class="ovr__label">{{ row.labelKey | translate }}</span>
23408
+ <span class="ovr__value">{{ row.value }}</span>
23409
+ </div>
23410
+ }
23411
+ <ng-content />
23412
+ </div>
23413
+ `, styles: [":host{display:block}.ovr{display:flex;flex-direction:column;gap:9px;padding:12px 14px;border-radius:11px;background:var(--w03);border:1px solid var(--w06)}.ovr ::ng-deep .ovr__row{display:flex;align-items:baseline;gap:14px}.ovr ::ng-deep .ovr__label{flex:0 0 auto;width:132px;font-family:var(--font-family);font-weight:var(--fw-medium);font-size:12px;line-height:1.4;color:var(--w45)}.ovr ::ng-deep .ovr__value{flex:1;min-width:0;font-family:var(--font-family);font-weight:var(--fw-medium);font-size:var(--text-sm);line-height:1.4;color:var(--w85)}\n"] }]
23414
+ }], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }] } });
23415
+
23416
+ /**
23417
+ * Project-lifecycle phase-gate stepper — the Overview-section kit's fourth
23418
+ * body variant. Summary/Description/Details are `fly-overview-kpi-row` /
23419
+ * `-surface` / `-rows`; this is the "Lifecycle" section's own body, per
23420
+ * `fly-overview-section`'s own doc comment ("Lifecycle's 'Move to' actions").
23421
+ * A caller resolves the gate vocabulary (approved/in-progress/not-started, …)
23422
+ * into an already-formatted `tone` per step — this component stays
23423
+ * domain-agnostic, same discipline as `OverviewKpiItem`/`OverviewRow`. Step
23424
+ * names accept either an i18n `labelKey` or a verbatim `label`, because real
23425
+ * lifecycles carry server-provided gate names that are data, not locale keys.
23426
+ *
23427
+ * The index badge and the trailing `fly-chip` always share one color, driven
23428
+ * by the SAME `tone` field, so the two never drift out of sync. A
23429
+ * `success`-toned step swaps its numeral for a check mark. The optional
23430
+ * rollup strip is a flat tally legend, not another `fly-overview-rows` (that
23431
+ * component's 132px label column is built for a vertical "Details" block,
23432
+ * not an inline summary strip).
23433
+ *
23434
+ * ```html
23435
+ * <fly-lifecycle-pipeline ariaLabelKey="ppm.projects.lifecycle.stagesLabel" [steps]="[
23436
+ * { labelKey: 'ppm.projects.lifecycle.phase1Gate', statusLabelKey: 'common.status.approved', tone: 'success', progress: '1 / 1' },
23437
+ * { labelKey: 'ppm.projects.lifecycle.phase2Gate', statusLabelKey: 'ppm.projects.lifecycle.status.inProgress', tone: 'accent', current: true },
23438
+ * { labelKey: 'ppm.projects.lifecycle.closureGate', statusLabelKey: 'ppm.projects.lifecycle.status.notStarted', tone: 'neutral' },
23439
+ * ]" [rollup]="[
23440
+ * { labelKey: 'ppm.projects.lifecycle.rollup.planned', value: 2 },
23441
+ * { labelKey: 'ppm.projects.lifecycle.rollup.inProgress', value: 2 },
23442
+ * ]" />
23443
+ * ```
23444
+ */
23445
+ class FlyLifecyclePipelineComponent {
23446
+ steps = input.required(...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
23447
+ rollup = input([], ...(ngDevMode ? [{ debugName: "rollup" }] : /* istanbul ignore next */ []));
23448
+ /** i18n key for the `role="list"` accessible name. */
23449
+ ariaLabelKey = input.required(...(ngDevMode ? [{ debugName: "ariaLabelKey" }] : /* istanbul ignore next */ []));
23450
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyLifecyclePipelineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23451
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyLifecyclePipelineComponent, isStandalone: true, selector: "fly-lifecycle-pipeline", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, rollup: { classPropertyName: "rollup", publicName: "rollup", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
23452
+ <div class="lp">
23453
+ <div class="lp__row" role="list" [attr.aria-label]="ariaLabelKey() | translate">
23454
+ @for (
23455
+ step of steps();
23456
+ track step.label ?? step.labelKey;
23457
+ let i = $index;
23458
+ let last = $last
23459
+ ) {
23460
+ <div
23461
+ class="lp__step"
23462
+ [class.lp__step--current]="step.current"
23463
+ role="listitem"
23464
+ [attr.aria-current]="step.current ? 'step' : null"
23465
+ >
23466
+ <span class="lp__index" [attr.data-tone]="step.tone">
23467
+ @if (step.tone === 'success') {
23468
+ <i class="pi pi-check" aria-hidden="true"></i>
23469
+ } @else {
23470
+ {{ i + 1 }}
23471
+ }
23472
+ </span>
23473
+ <span class="lp__name">
23474
+ @if (step.label) {
23475
+ {{ step.label }}
23476
+ } @else if (step.labelKey) {
23477
+ {{ step.labelKey | translate }}
23478
+ }
23479
+ </span>
23480
+ <fly-chip [tone]="step.tone">{{ step.statusLabelKey | translate }}</fly-chip>
23481
+ @if (step.progress) {
23482
+ <span class="lp__progress">{{ step.progress }}</span>
23483
+ }
23484
+ </div>
23485
+ @if (!last) {
23486
+ <span class="lp__connector" aria-hidden="true"></span>
23487
+ }
23488
+ }
23489
+ </div>
23490
+ @if (rollup().length) {
23491
+ <div class="lp__rollup">
23492
+ @for (item of rollup(); track item.labelKey) {
23493
+ <span class="lp__rollup-item">
23494
+ <span class="lp__rollup-label">{{ item.labelKey | translate }}</span>
23495
+ <span class="lp__rollup-value">{{ item.value }}</span>
23496
+ </span>
23497
+ }
23498
+ </div>
23499
+ }
23500
+ </div>
23501
+ `, isInline: true, styles: [":host{display:block}.lp{display:flex;flex-direction:column;gap:var(--sp-3)}.lp__row{display:flex;align-items:center;flex-wrap:wrap;row-gap:var(--sp-3)}.lp__step{display:flex;align-items:center;gap:var(--sp-2);padding:6px 10px;border-radius:var(--r-lg);border:1px solid transparent}.lp__step--current{border-color:var(--accent-fill);background:var(--tint-sel)}.lp__index{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:22px;height:22px;border-radius:999px;font-family:var(--font-family);font-size:11px;font-weight:var(--fw-bold);line-height:1}.lp__index .pi{font-size:10px}.lp__index[data-tone=neutral]{background:var(--w06);color:var(--w85)}.lp__index[data-tone=accent]{background:var(--accent-fill);color:var(--on-accent-fill)}.lp__index[data-tone=success]{background:var(--success-bg);color:var(--success)}.lp__index[data-tone=warning]{background:var(--warning-bg);color:var(--warning-fg)}.lp__index[data-tone=danger]{background:var(--danger-bg);color:var(--danger)}.lp__name{font-family:var(--font-family);font-weight:var(--fw-medium);font-size:var(--text-sm);color:var(--ink);white-space:nowrap}.lp__step--current .lp__name{font-weight:var(--fw-bold)}.lp__progress{font-family:var(--font-family);font-size:var(--text-xs);color:var(--w45);font-variant-numeric:tabular-nums}.lp__connector{flex:1 1 32px;min-width:20px;height:1px;background:var(--w16);margin-inline:2px}.lp__rollup{display:flex;flex-wrap:wrap;gap:var(--sp-1) var(--sp-4);padding-block-start:var(--sp-2);border-block-start:1px solid var(--w06)}.lp__rollup-item{display:inline-flex;align-items:baseline;gap:5px;font-family:var(--font-family);font-size:var(--text-xs)}.lp__rollup-label{color:var(--w45)}.lp__rollup-value{color:var(--w85);font-weight:var(--fw-bold);font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "component", type: FlyChipComponent, selector: "fly-chip", inputs: ["tone"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23502
+ }
23503
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyLifecyclePipelineComponent, decorators: [{
23504
+ type: Component,
23505
+ args: [{ selector: 'fly-lifecycle-pipeline', standalone: true, imports: [TranslatePipe, FlyChipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
23506
+ <div class="lp">
23507
+ <div class="lp__row" role="list" [attr.aria-label]="ariaLabelKey() | translate">
23508
+ @for (
23509
+ step of steps();
23510
+ track step.label ?? step.labelKey;
23511
+ let i = $index;
23512
+ let last = $last
23513
+ ) {
23514
+ <div
23515
+ class="lp__step"
23516
+ [class.lp__step--current]="step.current"
23517
+ role="listitem"
23518
+ [attr.aria-current]="step.current ? 'step' : null"
23519
+ >
23520
+ <span class="lp__index" [attr.data-tone]="step.tone">
23521
+ @if (step.tone === 'success') {
23522
+ <i class="pi pi-check" aria-hidden="true"></i>
23523
+ } @else {
23524
+ {{ i + 1 }}
23525
+ }
23526
+ </span>
23527
+ <span class="lp__name">
23528
+ @if (step.label) {
23529
+ {{ step.label }}
23530
+ } @else if (step.labelKey) {
23531
+ {{ step.labelKey | translate }}
23532
+ }
23533
+ </span>
23534
+ <fly-chip [tone]="step.tone">{{ step.statusLabelKey | translate }}</fly-chip>
23535
+ @if (step.progress) {
23536
+ <span class="lp__progress">{{ step.progress }}</span>
23537
+ }
23538
+ </div>
23539
+ @if (!last) {
23540
+ <span class="lp__connector" aria-hidden="true"></span>
23541
+ }
23542
+ }
23543
+ </div>
23544
+ @if (rollup().length) {
23545
+ <div class="lp__rollup">
23546
+ @for (item of rollup(); track item.labelKey) {
23547
+ <span class="lp__rollup-item">
23548
+ <span class="lp__rollup-label">{{ item.labelKey | translate }}</span>
23549
+ <span class="lp__rollup-value">{{ item.value }}</span>
23550
+ </span>
23551
+ }
23552
+ </div>
23553
+ }
23554
+ </div>
23555
+ `, styles: [":host{display:block}.lp{display:flex;flex-direction:column;gap:var(--sp-3)}.lp__row{display:flex;align-items:center;flex-wrap:wrap;row-gap:var(--sp-3)}.lp__step{display:flex;align-items:center;gap:var(--sp-2);padding:6px 10px;border-radius:var(--r-lg);border:1px solid transparent}.lp__step--current{border-color:var(--accent-fill);background:var(--tint-sel)}.lp__index{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:22px;height:22px;border-radius:999px;font-family:var(--font-family);font-size:11px;font-weight:var(--fw-bold);line-height:1}.lp__index .pi{font-size:10px}.lp__index[data-tone=neutral]{background:var(--w06);color:var(--w85)}.lp__index[data-tone=accent]{background:var(--accent-fill);color:var(--on-accent-fill)}.lp__index[data-tone=success]{background:var(--success-bg);color:var(--success)}.lp__index[data-tone=warning]{background:var(--warning-bg);color:var(--warning-fg)}.lp__index[data-tone=danger]{background:var(--danger-bg);color:var(--danger)}.lp__name{font-family:var(--font-family);font-weight:var(--fw-medium);font-size:var(--text-sm);color:var(--ink);white-space:nowrap}.lp__step--current .lp__name{font-weight:var(--fw-bold)}.lp__progress{font-family:var(--font-family);font-size:var(--text-xs);color:var(--w45);font-variant-numeric:tabular-nums}.lp__connector{flex:1 1 32px;min-width:20px;height:1px;background:var(--w16);margin-inline:2px}.lp__rollup{display:flex;flex-wrap:wrap;gap:var(--sp-1) var(--sp-4);padding-block-start:var(--sp-2);border-block-start:1px solid var(--w06)}.lp__rollup-item{display:inline-flex;align-items:baseline;gap:5px;font-family:var(--font-family);font-size:var(--text-xs)}.lp__rollup-label{color:var(--w45)}.lp__rollup-value{color:var(--w85);font-weight:var(--fw-bold);font-variant-numeric:tabular-nums}\n"] }]
23556
+ }], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], rollup: [{ type: i0.Input, args: [{ isSignal: true, alias: "rollup", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: true }] }] } });
23557
+
23106
23558
  function canGoPrev$1(page) {
23107
23559
  return page > 1;
23108
23560
  }
@@ -24251,6 +24703,65 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
24251
24703
  `, styles: [":host{display:block}.sh{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 16px;border-block-end:1px solid var(--line-3)}.sh__t{margin:0;font-size:13px;font-weight:var(--fw-semibold);color:var(--ink);letter-spacing:-.005em;min-width:0}.sh__a{display:inline-flex;align-items:center;gap:var(--sp-1);flex-shrink:0}.sh__a ::ng-deep a:not([fly-button]),.sh__a ::ng-deep button:not([fly-button],[fly-icon-button]){display:inline-flex;align-items:center;gap:5px;background:transparent;border:0;color:var(--accent);font-size:12px;font-weight:var(--fw-medium);cursor:pointer;padding:4px 6px;border-radius:var(--r-sm);font-family:inherit;text-decoration:none;transition:background var(--t-state)}.sh__a ::ng-deep a:not([fly-button]):hover,.sh__a ::ng-deep button:not([fly-button],[fly-icon-button]):hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}.sh__a ::ng-deep a:not([fly-button]):focus-visible,.sh__a ::ng-deep button:not([fly-button],[fly-icon-button]):focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}\n"] }]
24252
24704
  }], propDecorators: { titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }] } });
24253
24705
 
24706
+ /**
24707
+ * Shared shell for a Project-Details-style "Overview" page: a flat stack of
24708
+ * sections separated by hairlines — deliberately NOT a stack of cards. Every
24709
+ * section gets the same quiet eyebrow label; the body (stats / text / rows /
24710
+ * chips / members / …) is left to content projection, so a section that
24711
+ * carries real interaction (Members' add/remove, Lifecycle's "Move to"
24712
+ * actions) keeps its own live markup untouched. This component owns only the
24713
+ * shell + eyebrow + divider rhythm, never the variant bodies.
24714
+ *
24715
+ * First/last spacing resolves from DOM position (`:first-of-type` /
24716
+ * `:last-of-type` on the host tag), not an input — sections render from a
24717
+ * list, and a caller reordering or filtering that list should never also
24718
+ * have to recompute an explicit "am I first" flag.
24719
+ *
24720
+ * ```html
24721
+ * <fly-overview-section titleKey="ppm.projects.overview.summary">
24722
+ * <fly-overview-kpi-row [items]="kpis()" />
24723
+ * </fly-overview-section>
24724
+ *
24725
+ * <fly-overview-section titleKey="ppm.projects.overview.description">
24726
+ * <fly-overview-surface [proseText]="true">{{ project().description }}</fly-overview-surface>
24727
+ * </fly-overview-section>
24728
+ *
24729
+ * <!-- Sections with their own live interaction just project their existing
24730
+ * markup — the shell never dictates what a Members/Lifecycle body renders. -->
24731
+ * <fly-overview-section titleKey="ppm.projects.overview.members">
24732
+ * <fly-overview-surface>
24733
+ * <app-project-members-list [members]="members()" (removed)="onRemove($event)" />
24734
+ * </fly-overview-surface>
24735
+ * </fly-overview-section>
24736
+ * ```
24737
+ */
24738
+ class FlyOverviewSectionComponent {
24739
+ /** i18n key for the eyebrow label; alternatively project `[section-title]`. */
24740
+ titleKey = input(...(ngDevMode ? [undefined, { debugName: "titleKey" }] : /* istanbul ignore next */ []));
24741
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyOverviewSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
24742
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyOverviewSectionComponent, isStandalone: true, selector: "fly-overview-section", inputs: { titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
24743
+ <h2 class="ovs__eyebrow">
24744
+ @if (titleKey(); as key) {
24745
+ {{ key | translate }}
24746
+ }
24747
+ <ng-content select="[section-title]" />
24748
+ </h2>
24749
+ <ng-content />
24750
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:var(--sp-2);padding:var(--sp-4) 0;border-block-end:1px solid var(--w06)}:host:first-of-type{padding-block-start:0}:host:last-of-type{border-block-end:0}.ovs__eyebrow{margin:0;font-family:var(--font-family);font-weight:var(--fw-bold);font-size:10.5px;line-height:1;letter-spacing:.03em;color:var(--w42)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
24751
+ }
24752
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyOverviewSectionComponent, decorators: [{
24753
+ type: Component,
24754
+ args: [{ selector: 'fly-overview-section', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
24755
+ <h2 class="ovs__eyebrow">
24756
+ @if (titleKey(); as key) {
24757
+ {{ key | translate }}
24758
+ }
24759
+ <ng-content select="[section-title]" />
24760
+ </h2>
24761
+ <ng-content />
24762
+ `, styles: [":host{display:flex;flex-direction:column;gap:var(--sp-2);padding:var(--sp-4) 0;border-block-end:1px solid var(--w06)}:host:first-of-type{padding-block-start:0}:host:last-of-type{border-block-end:0}.ovs__eyebrow{margin:0;font-family:var(--font-family);font-weight:var(--fw-bold);font-size:10.5px;line-height:1;letter-spacing:.03em;color:var(--w42)}\n"] }]
24763
+ }], propDecorators: { titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }] } });
24764
+
24254
24765
  /**
24255
24766
  * Detail-page content card — the surface every panel and sidebar block on a detail
24256
24767
  * screen sits on: `card-surface` fill/hairline/radius/shadow, an optional
@@ -24291,30 +24802,30 @@ class FlyDetailCardComponent {
24291
24802
  /** Drop the body padding — for a child that brings its own list/table chrome. */
24292
24803
  flush = input(false, ...(ngDevMode ? [{ debugName: "flush" }] : /* istanbul ignore next */ []));
24293
24804
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDetailCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
24294
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDetailCardComponent, isStandalone: true, selector: "fly-detail-card", inputs: { titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, hasProjectedTitle: { classPropertyName: "hasProjectedTitle", publicName: "hasProjectedTitle", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
24295
- @if (titleKey() || hasProjectedTitle()) {
24296
- <fly-section-header [titleKey]="titleKey()">
24297
- <ng-content select="[card-title]" ngProjectAs="[section-title]" />
24298
- <ng-content select="[card-actions]" ngProjectAs="[section-actions]" />
24299
- </fly-section-header>
24300
- }
24301
- <div class="dc__body" [class.dc__body--flush]="flush()">
24302
- <ng-content />
24303
- </div>
24805
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDetailCardComponent, isStandalone: true, selector: "fly-detail-card", inputs: { titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, hasProjectedTitle: { classPropertyName: "hasProjectedTitle", publicName: "hasProjectedTitle", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
24806
+ @if (titleKey() || hasProjectedTitle()) {
24807
+ <fly-section-header [titleKey]="titleKey()">
24808
+ <ng-content select="[card-title]" ngProjectAs="[section-title]" />
24809
+ <ng-content select="[card-actions]" ngProjectAs="[section-actions]" />
24810
+ </fly-section-header>
24811
+ }
24812
+ <div class="dc__body" [class.dc__body--flush]="flush()">
24813
+ <ng-content />
24814
+ </div>
24304
24815
  `, isInline: true, styles: [":host{display:block;background:var(--bg-2);border:1px solid var(--w08);border-radius:var(--r-lg);box-shadow:var(--shadow-card);overflow:hidden;animation:itemIn .3s var(--nova-ease-micro) both}.dc__body{padding:var(--sp-3) var(--sp-4) var(--sp-4)}.dc__body--flush{padding:0}\n"], dependencies: [{ kind: "component", type: FlySectionHeaderComponent, selector: "fly-section-header", inputs: ["titleKey"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
24305
24816
  }
24306
24817
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDetailCardComponent, decorators: [{
24307
24818
  type: Component,
24308
- args: [{ selector: 'fly-detail-card', standalone: true, imports: [FlySectionHeaderComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
24309
- @if (titleKey() || hasProjectedTitle()) {
24310
- <fly-section-header [titleKey]="titleKey()">
24311
- <ng-content select="[card-title]" ngProjectAs="[section-title]" />
24312
- <ng-content select="[card-actions]" ngProjectAs="[section-actions]" />
24313
- </fly-section-header>
24314
- }
24315
- <div class="dc__body" [class.dc__body--flush]="flush()">
24316
- <ng-content />
24317
- </div>
24819
+ args: [{ selector: 'fly-detail-card', standalone: true, imports: [FlySectionHeaderComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
24820
+ @if (titleKey() || hasProjectedTitle()) {
24821
+ <fly-section-header [titleKey]="titleKey()">
24822
+ <ng-content select="[card-title]" ngProjectAs="[section-title]" />
24823
+ <ng-content select="[card-actions]" ngProjectAs="[section-actions]" />
24824
+ </fly-section-header>
24825
+ }
24826
+ <div class="dc__body" [class.dc__body--flush]="flush()">
24827
+ <ng-content />
24828
+ </div>
24318
24829
  `, styles: [":host{display:block;background:var(--bg-2);border:1px solid var(--w08);border-radius:var(--r-lg);box-shadow:var(--shadow-card);overflow:hidden;animation:itemIn .3s var(--nova-ease-micro) both}.dc__body{padding:var(--sp-3) var(--sp-4) var(--sp-4)}.dc__body--flush{padding:0}\n"] }]
24319
24830
  }], propDecorators: { titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], hasProjectedTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasProjectedTitle", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }] } });
24320
24831
 
@@ -24442,108 +24953,108 @@ class FlyDetailShellComponent {
24442
24953
  buttons?.[next]?.focus();
24443
24954
  }
24444
24955
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDetailShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
24445
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDetailShellComponent, isStandalone: true, selector: "fly-detail-shell", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, sectionsLabelKey: { classPropertyName: "sectionsLabelKey", publicName: "sectionsLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange" }, viewQueries: [{ propertyName: "rail", first: true, predicate: ["rail"], descendants: true, isSignal: true }], ngImport: i0, template: `
24446
- <div class="ds__layout">
24447
- <aside class="ds__aside">
24448
- <div class="ds__pinned">
24449
- <ng-content select="[detail-aside]" />
24450
- </div>
24451
-
24452
- @if (sections().length > 0) {
24453
- <div
24454
- #rail
24455
- class="ds__rail"
24456
- role="tablist"
24457
- tabindex="-1"
24458
- [attr.aria-orientation]="'vertical'"
24459
- [attr.aria-label]="sectionsLabelKey() | translate"
24460
- (keydown)="onKey($event)"
24461
- >
24462
- @for (s of sections(); track s.id) {
24463
- <button
24464
- type="button"
24465
- class="ds__tab"
24466
- role="tab"
24467
- [id]="tabId(s.id)"
24468
- [class.ds__tab--active]="activeId() === s.id"
24469
- [attr.aria-selected]="activeId() === s.id"
24470
- [attr.aria-controls]="panelId()"
24471
- [attr.tabindex]="activeId() === s.id ? 0 : -1"
24472
- [attr.title]="s.labelKey | translate"
24473
- (click)="select(s.id)"
24474
- >
24475
- @if (s.icon) {
24476
- <span class="pi {{ s.icon }} ds__tab-ico" aria-hidden="true"></span>
24477
- }
24478
- <span class="ds__tab-label">{{ s.labelKey | translate }}</span>
24479
- </button>
24480
- }
24481
- </div>
24482
- }
24483
- </aside>
24484
-
24485
- <div
24486
- class="ds__panel"
24487
- [id]="panelId()"
24488
- [attr.role]="sections().length ? 'tabpanel' : null"
24489
- [attr.aria-labelledby]="activeId() ? tabId(activeId()!) : null"
24490
- >
24491
- <ng-content />
24492
- </div>
24493
- </div>
24956
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDetailShellComponent, isStandalone: true, selector: "fly-detail-shell", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, sectionsLabelKey: { classPropertyName: "sectionsLabelKey", publicName: "sectionsLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange" }, viewQueries: [{ propertyName: "rail", first: true, predicate: ["rail"], descendants: true, isSignal: true }], ngImport: i0, template: `
24957
+ <div class="ds__layout">
24958
+ <aside class="ds__aside">
24959
+ <div class="ds__pinned">
24960
+ <ng-content select="[detail-aside]" />
24961
+ </div>
24962
+
24963
+ @if (sections().length > 0) {
24964
+ <div
24965
+ #rail
24966
+ class="ds__rail"
24967
+ role="tablist"
24968
+ tabindex="-1"
24969
+ [attr.aria-orientation]="'vertical'"
24970
+ [attr.aria-label]="sectionsLabelKey() | translate"
24971
+ (keydown)="onKey($event)"
24972
+ >
24973
+ @for (s of sections(); track s.id) {
24974
+ <button
24975
+ type="button"
24976
+ class="ds__tab"
24977
+ role="tab"
24978
+ [id]="tabId(s.id)"
24979
+ [class.ds__tab--active]="activeId() === s.id"
24980
+ [attr.aria-selected]="activeId() === s.id"
24981
+ [attr.aria-controls]="panelId()"
24982
+ [attr.tabindex]="activeId() === s.id ? 0 : -1"
24983
+ [attr.title]="s.labelKey | translate"
24984
+ (click)="select(s.id)"
24985
+ >
24986
+ @if (s.icon) {
24987
+ <span class="pi {{ s.icon }} ds__tab-ico" aria-hidden="true"></span>
24988
+ }
24989
+ <span class="ds__tab-label">{{ s.labelKey | translate }}</span>
24990
+ </button>
24991
+ }
24992
+ </div>
24993
+ }
24994
+ </aside>
24995
+
24996
+ <div
24997
+ class="ds__panel"
24998
+ [id]="panelId()"
24999
+ [attr.role]="sections().length ? 'tabpanel' : null"
25000
+ [attr.aria-labelledby]="activeId() ? tabId(activeId()!) : null"
25001
+ >
25002
+ <ng-content />
25003
+ </div>
25004
+ </div>
24494
25005
  `, isInline: true, styles: [":host{display:block;container-type:inline-size}.ds__layout{display:grid;grid-template-columns:var(--fly-detail-aside, 300px) minmax(0,1fr);gap:var(--sp-4);align-items:start}.ds__aside{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__aside .ds__rail{order:-1}.ds__pinned{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__panel{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0;align-self:stretch}.ds__panel>:only-child{flex:1}.ds__rail{display:flex;flex-direction:column;gap:var(--sp-1);padding:var(--sp-2);background:var(--bg-2);border:1px solid var(--w08);border-radius:var(--r-lg);box-shadow:var(--shadow-card)}.ds__tab{display:flex;align-items:center;gap:var(--sp-3);width:100%;padding:var(--sp-2);border:0;background:transparent;border-radius:var(--r-md);color:var(--ink-2);cursor:pointer;font-family:inherit;font-size:var(--text-sm);text-align:start;white-space:nowrap;overflow:hidden;transition:background var(--t-state),color var(--t-state)}.ds__tab:hover{background:var(--w06);color:var(--ink)}.ds__tab:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ds__tab--active{background:var(--accent-fill);color:var(--on-accent-fill);font-weight:var(--fw-semibold)}.ds__tab-ico{font-size:var(--text-md);flex-shrink:0;width:20px;text-align:center}.ds__tab-label{min-width:0;overflow:hidden;text-overflow:ellipsis}@container (width <= 980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-flow:row wrap}.ds__tab{width:auto}}@media(width<=980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-flow:row wrap}.ds__tab{width:auto}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
24495
25006
  }
24496
25007
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDetailShellComponent, decorators: [{
24497
25008
  type: Component,
24498
- args: [{ selector: 'fly-detail-shell', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
24499
- <div class="ds__layout">
24500
- <aside class="ds__aside">
24501
- <div class="ds__pinned">
24502
- <ng-content select="[detail-aside]" />
24503
- </div>
24504
-
24505
- @if (sections().length > 0) {
24506
- <div
24507
- #rail
24508
- class="ds__rail"
24509
- role="tablist"
24510
- tabindex="-1"
24511
- [attr.aria-orientation]="'vertical'"
24512
- [attr.aria-label]="sectionsLabelKey() | translate"
24513
- (keydown)="onKey($event)"
24514
- >
24515
- @for (s of sections(); track s.id) {
24516
- <button
24517
- type="button"
24518
- class="ds__tab"
24519
- role="tab"
24520
- [id]="tabId(s.id)"
24521
- [class.ds__tab--active]="activeId() === s.id"
24522
- [attr.aria-selected]="activeId() === s.id"
24523
- [attr.aria-controls]="panelId()"
24524
- [attr.tabindex]="activeId() === s.id ? 0 : -1"
24525
- [attr.title]="s.labelKey | translate"
24526
- (click)="select(s.id)"
24527
- >
24528
- @if (s.icon) {
24529
- <span class="pi {{ s.icon }} ds__tab-ico" aria-hidden="true"></span>
24530
- }
24531
- <span class="ds__tab-label">{{ s.labelKey | translate }}</span>
24532
- </button>
24533
- }
24534
- </div>
24535
- }
24536
- </aside>
24537
-
24538
- <div
24539
- class="ds__panel"
24540
- [id]="panelId()"
24541
- [attr.role]="sections().length ? 'tabpanel' : null"
24542
- [attr.aria-labelledby]="activeId() ? tabId(activeId()!) : null"
24543
- >
24544
- <ng-content />
24545
- </div>
24546
- </div>
25009
+ args: [{ selector: 'fly-detail-shell', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
25010
+ <div class="ds__layout">
25011
+ <aside class="ds__aside">
25012
+ <div class="ds__pinned">
25013
+ <ng-content select="[detail-aside]" />
25014
+ </div>
25015
+
25016
+ @if (sections().length > 0) {
25017
+ <div
25018
+ #rail
25019
+ class="ds__rail"
25020
+ role="tablist"
25021
+ tabindex="-1"
25022
+ [attr.aria-orientation]="'vertical'"
25023
+ [attr.aria-label]="sectionsLabelKey() | translate"
25024
+ (keydown)="onKey($event)"
25025
+ >
25026
+ @for (s of sections(); track s.id) {
25027
+ <button
25028
+ type="button"
25029
+ class="ds__tab"
25030
+ role="tab"
25031
+ [id]="tabId(s.id)"
25032
+ [class.ds__tab--active]="activeId() === s.id"
25033
+ [attr.aria-selected]="activeId() === s.id"
25034
+ [attr.aria-controls]="panelId()"
25035
+ [attr.tabindex]="activeId() === s.id ? 0 : -1"
25036
+ [attr.title]="s.labelKey | translate"
25037
+ (click)="select(s.id)"
25038
+ >
25039
+ @if (s.icon) {
25040
+ <span class="pi {{ s.icon }} ds__tab-ico" aria-hidden="true"></span>
25041
+ }
25042
+ <span class="ds__tab-label">{{ s.labelKey | translate }}</span>
25043
+ </button>
25044
+ }
25045
+ </div>
25046
+ }
25047
+ </aside>
25048
+
25049
+ <div
25050
+ class="ds__panel"
25051
+ [id]="panelId()"
25052
+ [attr.role]="sections().length ? 'tabpanel' : null"
25053
+ [attr.aria-labelledby]="activeId() ? tabId(activeId()!) : null"
25054
+ >
25055
+ <ng-content />
25056
+ </div>
25057
+ </div>
24547
25058
  `, styles: [":host{display:block;container-type:inline-size}.ds__layout{display:grid;grid-template-columns:var(--fly-detail-aside, 300px) minmax(0,1fr);gap:var(--sp-4);align-items:start}.ds__aside{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__aside .ds__rail{order:-1}.ds__pinned{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__panel{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0;align-self:stretch}.ds__panel>:only-child{flex:1}.ds__rail{display:flex;flex-direction:column;gap:var(--sp-1);padding:var(--sp-2);background:var(--bg-2);border:1px solid var(--w08);border-radius:var(--r-lg);box-shadow:var(--shadow-card)}.ds__tab{display:flex;align-items:center;gap:var(--sp-3);width:100%;padding:var(--sp-2);border:0;background:transparent;border-radius:var(--r-md);color:var(--ink-2);cursor:pointer;font-family:inherit;font-size:var(--text-sm);text-align:start;white-space:nowrap;overflow:hidden;transition:background var(--t-state),color var(--t-state)}.ds__tab:hover{background:var(--w06);color:var(--ink)}.ds__tab:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ds__tab--active{background:var(--accent-fill);color:var(--on-accent-fill);font-weight:var(--fw-semibold)}.ds__tab-ico{font-size:var(--text-md);flex-shrink:0;width:20px;text-align:center}.ds__tab-label{min-width:0;overflow:hidden;text-overflow:ellipsis}@container (width <= 980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-flow:row wrap}.ds__tab{width:auto}}@media(width<=980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-flow:row wrap}.ds__tab{width:auto}}\n"] }]
24548
25059
  }], propDecorators: { rail: [{ type: i0.ViewChild, args: ['rail', { isSignal: true }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], sectionsLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionsLabelKey", required: false }] }] } });
24549
25060
 
@@ -24960,6 +25471,15 @@ function sectionHintKey(section, expanded) {
24960
25471
  * Icons reuse `flyModuleIcon` — the SAME directive the topbar takes — so an app that
24961
25472
  * renders both declares its icon templates once per surface, in the same syntax.
24962
25473
  *
25474
+ * ## App colour (3.12.0)
25475
+ *
25476
+ * The launch-card family (feature tile, hover wash, hover ring) follows the app's
25477
+ * authored brand colour, received by CSS CASCADE rather than input: the shell window
25478
+ * publishes the app's derived palette (`--app-color`, `--app-color-deep`, `--app-ink`)
25479
+ * on `.window`, so a federated landing is branded with zero wiring; a standalone host
25480
+ * brands itself by setting the same custom properties on any ancestor element. Where
25481
+ * nothing cascades, the fixed `--module-teal` family renders exactly as before.
25482
+ *
24963
25483
  * a11y: cards are real `<button>`s in a labelled group; a collapsible section is a
24964
25484
  * disclosure (`aria-expanded` + `aria-controls`), and the region carries the section
24965
25485
  * heading. Layout is logical-property only, so it mirrors under `dir="rtl"` on its own —
@@ -25015,11 +25535,11 @@ class FlyAppHomeComponent {
25015
25535
  return `fly-app-home-sec-${index}`;
25016
25536
  }
25017
25537
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppHomeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
25018
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppHomeComponent, isStandalone: true, selector: "fly-app-home", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, subtitleKey: { classPropertyName: "subtitleKey", publicName: "subtitleKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, modulesLabelKey: { classPropertyName: "modulesLabelKey", publicName: "modulesLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], ngImport: i0, template: "<div class=\"ah\">\r\n <header class=\"ah__util\">\r\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\r\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n <ng-content select=\"[app-home-actions]\" />\r\n </header>\r\n\r\n <main class=\"ah__main\">\r\n @if (titleKey() || subtitleKey()) {\r\n <section class=\"ah__hero\">\r\n @if (titleKey(); as key) {\r\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\r\n }\r\n @if (subtitleKey(); as key) {\r\n <p class=\"ah__sub\">{{ key | translate }}</p>\r\n }\r\n </section>\r\n }\r\n\r\n @for (row of rows(); track row.index) {\r\n <section\r\n class=\"ah__sec\"\r\n [class.ah__sec--secondary]=\"row.index > 0\"\r\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\r\n >\r\n @if (row.section.titleKey; as titleKey) {\r\n @if (row.section.collapsible) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__sec-hd\"\r\n (click)=\"toggle(row.index)\"\r\n [attr.aria-expanded]=\"row.expanded\"\r\n [attr.aria-controls]=\"sectionId(row.index)\"\r\n >\r\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\r\n flip below because a downward chevron has no direction to mirror. -->\r\n <svg\r\n class=\"ah__sec-chev\"\r\n [class.ah__sec-chev--open]=\"row.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\r\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\r\n @if (row.hintKey; as hint) {\r\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\r\n }\r\n }\r\n\r\n @if (row.expanded) {\r\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\r\n @for (mod of row.section.modules; track mod.key) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__card\"\r\n [class.ah__card--compact]=\"row.layout === 'compact'\"\r\n [disabled]=\"mod.disabled\"\r\n (click)=\"select(mod)\"\r\n >\r\n <span class=\"ah__card-ico\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n\r\n @if (row.layout === 'compact') {\r\n <span class=\"ah__card-main\">\r\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n } @else {\r\n <!-- No \"Open \u203A\" affordance row \u2014 the design's module card (MainContent.dc.html)\r\n ends at the description; the whole card is the click target and hover carries\r\n the affordance. Removed at the 2026-08-17 fidelity pass. -->\r\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\r\n }\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n </main>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}:host-context(.window-content) .ah__brand{display:none}:host-context(.window-content) .ah__util{justify-content:flex-end}:host-context(.window-content) .ah__util:not(:has(>:not(.ah__brand))){display:none}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--w08)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);font-variant-numeric:tabular-nums;color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:20px;background-image:linear-gradient(160deg,var(--w055),var(--w02));border:1px solid var(--w09);border-radius:20px;color:inherit;font-family:inherit;cursor:pointer;animation:appTileIn .3s var(--nova-ease-micro) both}.ah__card:nth-child(1){animation-delay:0ms}.ah__card:nth-child(2){animation-delay:20ms}.ah__card:nth-child(3){animation-delay:40ms}.ah__card:nth-child(4){animation-delay:60ms}.ah__card:nth-child(5){animation-delay:80ms}.ah__card:nth-child(6){animation-delay:.1s}.ah__card:nth-child(7){animation-delay:.12s}.ah__card:nth-child(8){animation-delay:.14s}.ah__card:nth-child(9){animation-delay:.16s}.ah__card:nth-child(10){animation-delay:.18s}.ah__card:nth-child(11){animation-delay:.2s}.ah__card:nth-child(12){animation-delay:.22s}.ah__card:nth-child(n+13){animation-delay:.22s}.ah__card{transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-state),background-image var(--t-state)}.ah__card:hover:not(:disabled){transform:translateY(-3px);border-color:color-mix(in oklab,var(--accent) 50%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent);box-shadow:0 20px 46px color-mix(in oklab,var(--accent) 16%,transparent),var(--shadow-filter)}.ah__card:focus-visible{outline:0;border-color:color-mix(in oklab,var(--accent) 50%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent);box-shadow:0 0 0 3px var(--accent-soft),0 20px 46px color-mix(in oklab,var(--accent) 16%,transparent),var(--shadow-filter)}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:42px;block-size:42px;margin-block-end:16px;border-radius:13px;background:linear-gradient(140deg,color-mix(in srgb,var(--accent) 50%,transparent),color-mix(in srgb,var(--sys-teal) 22%,transparent));box-shadow:0 6px 16px color-mix(in srgb,var(--accent) 30%,transparent),inset 0 1px 0 var(--edge-highlight);border:0;color:var(--on-accent);font-size:var(--icon-lg)}.ah__card--compact:hover:not(:disabled) .ah__card-ico,.ah__card--compact:focus-visible .ah__card-ico{background:linear-gradient(135deg,var(--accent),var(--sys-teal));box-shadow:inset 0 1px 0 var(--edge-highlight);color:var(--on-accent)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-title--feature{margin-block-end:5px;font-size:16.5px;line-height:1.25;letter-spacing:-.005em}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{margin-block-end:0;font-size:13px;line-height:1.45;text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:14px 16px;background-color:var(--bg);background-image:linear-gradient(160deg,var(--w035),var(--w035));border-radius:16px;border-color:var(--w08)}.ah__card--compact:hover:not(:disabled),.ah__card--compact:focus-visible{border-color:color-mix(in oklab,var(--accent) 40%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent)}.ah__card--compact .ah__card-ico{inline-size:32px;block-size:32px;margin-block-end:0;flex-shrink:0;border-radius:10px;font-size:var(--icon-md);background:var(--bg-3);box-shadow:none;color:var(--ink)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
25538
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppHomeComponent, isStandalone: true, selector: "fly-app-home", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, subtitleKey: { classPropertyName: "subtitleKey", publicName: "subtitleKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, modulesLabelKey: { classPropertyName: "modulesLabelKey", publicName: "modulesLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], ngImport: i0, template: "<div class=\"ah\">\r\n <header class=\"ah__util\">\r\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\r\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n <ng-content select=\"[app-home-actions]\" />\r\n </header>\r\n\r\n <main class=\"ah__main\">\r\n @if (titleKey() || subtitleKey()) {\r\n <section class=\"ah__hero\">\r\n @if (titleKey(); as key) {\r\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\r\n }\r\n @if (subtitleKey(); as key) {\r\n <p class=\"ah__sub\">{{ key | translate }}</p>\r\n }\r\n </section>\r\n }\r\n\r\n @for (row of rows(); track row.index) {\r\n <section\r\n class=\"ah__sec\"\r\n [class.ah__sec--secondary]=\"row.index > 0\"\r\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\r\n >\r\n @if (row.section.titleKey; as titleKey) {\r\n @if (row.section.collapsible) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__sec-hd\"\r\n (click)=\"toggle(row.index)\"\r\n [attr.aria-expanded]=\"row.expanded\"\r\n [attr.aria-controls]=\"sectionId(row.index)\"\r\n >\r\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\r\n flip below because a downward chevron has no direction to mirror. -->\r\n <svg\r\n class=\"ah__sec-chev\"\r\n [class.ah__sec-chev--open]=\"row.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\r\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\r\n @if (row.hintKey; as hint) {\r\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\r\n }\r\n }\r\n\r\n @if (row.expanded) {\r\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\r\n @for (mod of row.section.modules; track mod.key) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__card\"\r\n [class.ah__card--compact]=\"row.layout === 'compact'\"\r\n [disabled]=\"mod.disabled\"\r\n (click)=\"select(mod)\"\r\n >\r\n <span class=\"ah__card-ico\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n\r\n @if (row.layout === 'compact') {\r\n <span class=\"ah__card-main\">\r\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n } @else {\r\n <!-- No \"Open \u203A\" affordance row \u2014 the design's module card (MainContent.dc.html)\r\n ends at the description; the whole card is the click target and hover carries\r\n the affordance. Removed at the 2026-08-17 fidelity pass. -->\r\n <!-- Text stack: icon-to-text spacing is `.ah__card`'s own gap; this wrapper's\r\n gap is title-to-description only (module-card fidelity pass, 2026-08-20). -->\r\n <span class=\"ah__card-text\">\r\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n </main>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size;--ah-brand: var(--app-color, var(--module-teal));--ah-brand-deep: var(--app-color-deep, var(--module-teal-deep));--ah-brand-soft: var(--app-color, var(--sys-teal));--ah-brand-ink: var(--app-ink, var(--ink-inverse))}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}:host-context(.window-content) .ah__brand{display:none}:host-context(.window-content) .ah__util{justify-content:flex-end}:host-context(.window-content) .ah__util:not(:has(>:not(.ah__brand))){display:none}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--w08)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);font-variant-numeric:tabular-nums;color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;margin-top:4px;justify-content:flex-start}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:20px;gap:16px;background:linear-gradient(160deg,var(--w055),var(--w02));border:1px solid var(--w09);border-radius:20px;color:inherit;font-family:inherit;cursor:pointer;animation:appTileIn .3s var(--nova-ease-micro) both}.ah__card:nth-child(1){animation-delay:0ms}.ah__card:nth-child(2){animation-delay:20ms}.ah__card:nth-child(3){animation-delay:40ms}.ah__card:nth-child(4){animation-delay:60ms}.ah__card:nth-child(5){animation-delay:80ms}.ah__card:nth-child(6){animation-delay:.1s}.ah__card:nth-child(7){animation-delay:.12s}.ah__card:nth-child(8){animation-delay:.14s}.ah__card:nth-child(9){animation-delay:.16s}.ah__card:nth-child(10){animation-delay:.18s}.ah__card:nth-child(11){animation-delay:.2s}.ah__card:nth-child(12){animation-delay:.22s}.ah__card:nth-child(n+13){animation-delay:.22s}.ah__card{transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-state),background var(--t-state)}.ah__card:hover:not(:disabled){transform:translateY(-3px);background:linear-gradient(160deg,color-mix(in srgb,var(--ah-brand) 22%,transparent),color-mix(in srgb,var(--ah-brand-soft) 5%,transparent));border-color:color-mix(in srgb,var(--ah-brand) 50%,transparent);box-shadow:inset 0 1px color-mix(in srgb,var(--edge-highlight) 20%,transparent),0 20px 46px color-mix(in srgb,var(--ah-brand) 16%,transparent),var(--shadow-filter)}.ah__card:focus-visible{outline:0;border-color:color-mix(in oklab,var(--accent) 50%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent);box-shadow:0 0 0 3px var(--accent-soft),0 20px 46px color-mix(in oklab,var(--accent) 16%,transparent),var(--shadow-filter)}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:42px;block-size:42px;flex:none;border-radius:13px;background:linear-gradient(140deg,color-mix(in srgb,var(--ah-brand-deep) 62%,transparent),color-mix(in srgb,var(--ah-brand) 30%,transparent));box-shadow:0 6px 16px color-mix(in srgb,var(--ah-brand-deep) 30%,transparent),inset 0 1px color-mix(in srgb,var(--edge-highlight) 50%,transparent);border:0;color:var(--ah-brand-ink);font-size:18px;transition:transform var(--t-overlay)}.ah__card--compact:hover:not(:disabled) .ah__card-ico,.ah__card--compact:focus-visible .ah__card-ico{background:linear-gradient(135deg,var(--accent),var(--sys-teal));box-shadow:inset 0 1px 0 var(--edge-highlight);color:var(--on-accent)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-text{display:flex;flex-direction:column;gap:5px}.ah__card-title--feature{font-family:var(--font-family);font-weight:var(--fw-semibold);font-size:16.5px;line-height:1.25;letter-spacing:-.005em;color:var(--ink)}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{font-family:var(--font-family);font-weight:400;font-size:13px;line-height:1.45;color:var(--w55);text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:14px 16px;background-color:var(--bg);background-image:linear-gradient(160deg,var(--w035),var(--w035));border-radius:16px;border-color:var(--w08)}.ah__card--compact:hover:not(:disabled),.ah__card--compact:focus-visible{border-color:color-mix(in oklab,var(--accent) 40%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent);box-shadow:0 20px 46px color-mix(in oklab,var(--accent) 16%,transparent),var(--shadow-filter)}.ah__card--compact .ah__card-ico{inline-size:32px;block-size:32px;margin-block-end:0;flex-shrink:0;border-radius:10px;font-size:var(--icon-md);background:var(--bg-3);box-shadow:none;color:var(--ink)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
25019
25539
  }
25020
25540
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppHomeComponent, decorators: [{
25021
25541
  type: Component,
25022
- args: [{ selector: 'fly-app-home', standalone: true, imports: [NgTemplateOutlet, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ah\">\r\n <header class=\"ah__util\">\r\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\r\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n <ng-content select=\"[app-home-actions]\" />\r\n </header>\r\n\r\n <main class=\"ah__main\">\r\n @if (titleKey() || subtitleKey()) {\r\n <section class=\"ah__hero\">\r\n @if (titleKey(); as key) {\r\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\r\n }\r\n @if (subtitleKey(); as key) {\r\n <p class=\"ah__sub\">{{ key | translate }}</p>\r\n }\r\n </section>\r\n }\r\n\r\n @for (row of rows(); track row.index) {\r\n <section\r\n class=\"ah__sec\"\r\n [class.ah__sec--secondary]=\"row.index > 0\"\r\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\r\n >\r\n @if (row.section.titleKey; as titleKey) {\r\n @if (row.section.collapsible) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__sec-hd\"\r\n (click)=\"toggle(row.index)\"\r\n [attr.aria-expanded]=\"row.expanded\"\r\n [attr.aria-controls]=\"sectionId(row.index)\"\r\n >\r\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\r\n flip below because a downward chevron has no direction to mirror. -->\r\n <svg\r\n class=\"ah__sec-chev\"\r\n [class.ah__sec-chev--open]=\"row.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\r\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\r\n @if (row.hintKey; as hint) {\r\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\r\n }\r\n }\r\n\r\n @if (row.expanded) {\r\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\r\n @for (mod of row.section.modules; track mod.key) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__card\"\r\n [class.ah__card--compact]=\"row.layout === 'compact'\"\r\n [disabled]=\"mod.disabled\"\r\n (click)=\"select(mod)\"\r\n >\r\n <span class=\"ah__card-ico\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n\r\n @if (row.layout === 'compact') {\r\n <span class=\"ah__card-main\">\r\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n } @else {\r\n <!-- No \"Open \u203A\" affordance row \u2014 the design's module card (MainContent.dc.html)\r\n ends at the description; the whole card is the click target and hover carries\r\n the affordance. Removed at the 2026-08-17 fidelity pass. -->\r\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\r\n }\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n </main>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}:host-context(.window-content) .ah__brand{display:none}:host-context(.window-content) .ah__util{justify-content:flex-end}:host-context(.window-content) .ah__util:not(:has(>:not(.ah__brand))){display:none}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--w08)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);font-variant-numeric:tabular-nums;color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:20px;background-image:linear-gradient(160deg,var(--w055),var(--w02));border:1px solid var(--w09);border-radius:20px;color:inherit;font-family:inherit;cursor:pointer;animation:appTileIn .3s var(--nova-ease-micro) both}.ah__card:nth-child(1){animation-delay:0ms}.ah__card:nth-child(2){animation-delay:20ms}.ah__card:nth-child(3){animation-delay:40ms}.ah__card:nth-child(4){animation-delay:60ms}.ah__card:nth-child(5){animation-delay:80ms}.ah__card:nth-child(6){animation-delay:.1s}.ah__card:nth-child(7){animation-delay:.12s}.ah__card:nth-child(8){animation-delay:.14s}.ah__card:nth-child(9){animation-delay:.16s}.ah__card:nth-child(10){animation-delay:.18s}.ah__card:nth-child(11){animation-delay:.2s}.ah__card:nth-child(12){animation-delay:.22s}.ah__card:nth-child(n+13){animation-delay:.22s}.ah__card{transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-state),background-image var(--t-state)}.ah__card:hover:not(:disabled){transform:translateY(-3px);border-color:color-mix(in oklab,var(--accent) 50%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent);box-shadow:0 20px 46px color-mix(in oklab,var(--accent) 16%,transparent),var(--shadow-filter)}.ah__card:focus-visible{outline:0;border-color:color-mix(in oklab,var(--accent) 50%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent);box-shadow:0 0 0 3px var(--accent-soft),0 20px 46px color-mix(in oklab,var(--accent) 16%,transparent),var(--shadow-filter)}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:42px;block-size:42px;margin-block-end:16px;border-radius:13px;background:linear-gradient(140deg,color-mix(in srgb,var(--accent) 50%,transparent),color-mix(in srgb,var(--sys-teal) 22%,transparent));box-shadow:0 6px 16px color-mix(in srgb,var(--accent) 30%,transparent),inset 0 1px 0 var(--edge-highlight);border:0;color:var(--on-accent);font-size:var(--icon-lg)}.ah__card--compact:hover:not(:disabled) .ah__card-ico,.ah__card--compact:focus-visible .ah__card-ico{background:linear-gradient(135deg,var(--accent),var(--sys-teal));box-shadow:inset 0 1px 0 var(--edge-highlight);color:var(--on-accent)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-title--feature{margin-block-end:5px;font-size:16.5px;line-height:1.25;letter-spacing:-.005em}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{margin-block-end:0;font-size:13px;line-height:1.45;text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:14px 16px;background-color:var(--bg);background-image:linear-gradient(160deg,var(--w035),var(--w035));border-radius:16px;border-color:var(--w08)}.ah__card--compact:hover:not(:disabled),.ah__card--compact:focus-visible{border-color:color-mix(in oklab,var(--accent) 40%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent)}.ah__card--compact .ah__card-ico{inline-size:32px;block-size:32px;margin-block-end:0;flex-shrink:0;border-radius:10px;font-size:var(--icon-md);background:var(--bg-3);box-shadow:none;color:var(--ink)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"] }]
25542
+ args: [{ selector: 'fly-app-home', standalone: true, imports: [NgTemplateOutlet, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ah\">\r\n <header class=\"ah__util\">\r\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\r\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n <ng-content select=\"[app-home-actions]\" />\r\n </header>\r\n\r\n <main class=\"ah__main\">\r\n @if (titleKey() || subtitleKey()) {\r\n <section class=\"ah__hero\">\r\n @if (titleKey(); as key) {\r\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\r\n }\r\n @if (subtitleKey(); as key) {\r\n <p class=\"ah__sub\">{{ key | translate }}</p>\r\n }\r\n </section>\r\n }\r\n\r\n @for (row of rows(); track row.index) {\r\n <section\r\n class=\"ah__sec\"\r\n [class.ah__sec--secondary]=\"row.index > 0\"\r\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\r\n >\r\n @if (row.section.titleKey; as titleKey) {\r\n @if (row.section.collapsible) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__sec-hd\"\r\n (click)=\"toggle(row.index)\"\r\n [attr.aria-expanded]=\"row.expanded\"\r\n [attr.aria-controls]=\"sectionId(row.index)\"\r\n >\r\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\r\n flip below because a downward chevron has no direction to mirror. -->\r\n <svg\r\n class=\"ah__sec-chev\"\r\n [class.ah__sec-chev--open]=\"row.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\r\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\r\n @if (row.hintKey; as hint) {\r\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\r\n }\r\n }\r\n\r\n @if (row.expanded) {\r\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\r\n @for (mod of row.section.modules; track mod.key) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__card\"\r\n [class.ah__card--compact]=\"row.layout === 'compact'\"\r\n [disabled]=\"mod.disabled\"\r\n (click)=\"select(mod)\"\r\n >\r\n <span class=\"ah__card-ico\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n\r\n @if (row.layout === 'compact') {\r\n <span class=\"ah__card-main\">\r\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n } @else {\r\n <!-- No \"Open \u203A\" affordance row \u2014 the design's module card (MainContent.dc.html)\r\n ends at the description; the whole card is the click target and hover carries\r\n the affordance. Removed at the 2026-08-17 fidelity pass. -->\r\n <!-- Text stack: icon-to-text spacing is `.ah__card`'s own gap; this wrapper's\r\n gap is title-to-description only (module-card fidelity pass, 2026-08-20). -->\r\n <span class=\"ah__card-text\">\r\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n </main>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size;--ah-brand: var(--app-color, var(--module-teal));--ah-brand-deep: var(--app-color-deep, var(--module-teal-deep));--ah-brand-soft: var(--app-color, var(--sys-teal));--ah-brand-ink: var(--app-ink, var(--ink-inverse))}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}:host-context(.window-content) .ah__brand{display:none}:host-context(.window-content) .ah__util{justify-content:flex-end}:host-context(.window-content) .ah__util:not(:has(>:not(.ah__brand))){display:none}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--w08)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid;outline-color:var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);font-variant-numeric:tabular-nums;color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;margin-top:4px;justify-content:flex-start}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:20px;gap:16px;background:linear-gradient(160deg,var(--w055),var(--w02));border:1px solid var(--w09);border-radius:20px;color:inherit;font-family:inherit;cursor:pointer;animation:appTileIn .3s var(--nova-ease-micro) both}.ah__card:nth-child(1){animation-delay:0ms}.ah__card:nth-child(2){animation-delay:20ms}.ah__card:nth-child(3){animation-delay:40ms}.ah__card:nth-child(4){animation-delay:60ms}.ah__card:nth-child(5){animation-delay:80ms}.ah__card:nth-child(6){animation-delay:.1s}.ah__card:nth-child(7){animation-delay:.12s}.ah__card:nth-child(8){animation-delay:.14s}.ah__card:nth-child(9){animation-delay:.16s}.ah__card:nth-child(10){animation-delay:.18s}.ah__card:nth-child(11){animation-delay:.2s}.ah__card:nth-child(12){animation-delay:.22s}.ah__card:nth-child(n+13){animation-delay:.22s}.ah__card{transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-state),background var(--t-state)}.ah__card:hover:not(:disabled){transform:translateY(-3px);background:linear-gradient(160deg,color-mix(in srgb,var(--ah-brand) 22%,transparent),color-mix(in srgb,var(--ah-brand-soft) 5%,transparent));border-color:color-mix(in srgb,var(--ah-brand) 50%,transparent);box-shadow:inset 0 1px color-mix(in srgb,var(--edge-highlight) 20%,transparent),0 20px 46px color-mix(in srgb,var(--ah-brand) 16%,transparent),var(--shadow-filter)}.ah__card:focus-visible{outline:0;border-color:color-mix(in oklab,var(--accent) 50%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent);box-shadow:0 0 0 3px var(--accent-soft),0 20px 46px color-mix(in oklab,var(--accent) 16%,transparent),var(--shadow-filter)}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:42px;block-size:42px;flex:none;border-radius:13px;background:linear-gradient(140deg,color-mix(in srgb,var(--ah-brand-deep) 62%,transparent),color-mix(in srgb,var(--ah-brand) 30%,transparent));box-shadow:0 6px 16px color-mix(in srgb,var(--ah-brand-deep) 30%,transparent),inset 0 1px color-mix(in srgb,var(--edge-highlight) 50%,transparent);border:0;color:var(--ah-brand-ink);font-size:18px;transition:transform var(--t-overlay)}.ah__card--compact:hover:not(:disabled) .ah__card-ico,.ah__card--compact:focus-visible .ah__card-ico{background:linear-gradient(135deg,var(--accent),var(--sys-teal));box-shadow:inset 0 1px 0 var(--edge-highlight);color:var(--on-accent)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-text{display:flex;flex-direction:column;gap:5px}.ah__card-title--feature{font-family:var(--font-family);font-weight:var(--fw-semibold);font-size:16.5px;line-height:1.25;letter-spacing:-.005em;color:var(--ink)}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{font-family:var(--font-family);font-weight:400;font-size:13px;line-height:1.45;color:var(--w55);text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:14px 16px;background-color:var(--bg);background-image:linear-gradient(160deg,var(--w035),var(--w035));border-radius:16px;border-color:var(--w08)}.ah__card--compact:hover:not(:disabled),.ah__card--compact:focus-visible{border-color:color-mix(in oklab,var(--accent) 40%,transparent);background-image:linear-gradient(160deg,var(--tint-hover),transparent);box-shadow:0 20px 46px color-mix(in oklab,var(--accent) 16%,transparent),var(--shadow-filter)}.ah__card--compact .ah__card-ico{inline-size:32px;block-size:32px;margin-block-end:0;flex-shrink:0;border-radius:10px;font-size:var(--icon-md);background:var(--bg-3);box-shadow:none;color:var(--ink)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"] }]
25023
25543
  }], propDecorators: { brandLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLabelKey", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], subtitleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitleKey", required: false }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], modulesLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "modulesLabelKey", required: false }] }], moduleSelected: [{ type: i0.Output, args: ["moduleSelected"] }], brandSelected: [{ type: i0.Output, args: ["brandSelected"] }], icons: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FlyModuleIconDirective), { isSignal: true }] }] } });
25024
25544
 
25025
25545
  /**
@@ -27447,5 +27967,5 @@ const AUDIENCE_ERROR_CODES = {
27447
27967
  * Generated bundle index. Do not edit.
27448
27968
  */
27449
27969
 
27450
- export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_CURRENCY_DEFAULT_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_BREADCRUMBS_EVENT, FLY_WINDOW_BREADCRUMBS_STORE_KEY, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPagerComponent, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyStrategySelectorComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowBreadcrumbsRegistry, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext$1 as canGoNext, canGoPrev$1 as canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage$1 as clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapCurrency, flyUnwrapLenient, flyUnwrapObjectiveLookup, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, canGoNext as pagerCanGoNext, canGoPrev as pagerCanGoPrev, clampPage as pagerClampPage, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
27970
+ export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_CURRENCY_DEFAULT_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_OUTLET_DEPTH, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_BREADCRUMBS_EVENT, FLY_WINDOW_BREADCRUMBS_STORE_KEY, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyLifecyclePipelineComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyOverviewKpiRowComponent, FlyOverviewRowsComponent, FlyOverviewSectionComponent, FlyOverviewSurfaceComponent, FlyPagerComponent, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyStrategySelectorComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowBreadcrumbsRegistry, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext$1 as canGoNext, canGoPrev$1 as canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage$1 as clampPage, clampSliderValue, connectRemoteLaunch, deepestFlyMatch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapCurrency, flyUnwrapLenient, flyUnwrapObjectiveLookup, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, matchFlyRoutePrefix, matchFlyRouteTable, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, canGoNext as pagerCanGoNext, canGoPrev as pagerCanGoPrev, clampPage as pagerClampPage, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyOfflineAuth, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
27451
27971
  //# sourceMappingURL=flyos-design-system.mjs.map