fold-ng 0.3.0 → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  import { DOCUMENT, NgTemplateOutlet, NgClass, DatePipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { input, inject, ElementRef, effect, Directive, Injectable, signal, DestroyRef, model, computed, HostListener, Component, InjectionToken, Service, booleanAttribute, numberAttribute, ViewEncapsulation, output, viewChild, afterNextRender, contentChildren, TemplateRef, isDevMode, contentChild, viewChildren, ViewContainerRef, Injector } from '@angular/core';
3
+ import { input, inject, ElementRef, effect, Directive, Injectable, signal, DestroyRef, model, computed, HostListener, Component, InjectionToken, Service, booleanAttribute, linkedSignal, numberAttribute, ViewEncapsulation, output, viewChild, afterNextRender, contentChildren, TemplateRef, isDevMode, contentChild, viewChildren, ViewContainerRef, Injector } from '@angular/core';
4
4
  import { RouterLink, RouterLinkActive } from '@angular/router';
5
5
 
6
6
  /**
@@ -638,32 +638,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
638
638
  type: Service
639
639
  }] });
640
640
 
641
- /** Ink for text on a light vs dark categorical fill. */
642
- const DARK_INK = "#1a202c";
643
- const LIGHT_INK = "#ffffff";
644
641
  /**
645
- * Pick a readable ink for text drawn on `fill`, from its relative luminance —
646
- * so a custom (dark) palette supplied via `provideFoldPalette` still gets legible
647
- * initials instead of the fixed dark ink. Non-hex fills fall back to dark ink.
642
+ * Small WCAG colour-contrast helpers — used to pick a legible ink for text on a
643
+ * categorical fill (avatar initials), and lockable by a contrast test.
644
+ *
645
+ * These operate on plain hex strings (`#rgb` / `#rrggbb`); a categorical palette
646
+ * is qualitative *data* (hex is allowed there — rule 5.3), so contrast can't be
647
+ * a token concern and lives here.
648
648
  */
649
- function readableInk(fill) {
650
- const hex = fill.trim().replace(/^#/, "");
651
- const full = hex.length === 3
652
- ? hex
649
+ /** Parse `#rgb` / `#rrggbb` to `[r, g, b]` (0–255), or `null` if not a hex. */
650
+ function parseHex(hex) {
651
+ const h = hex.trim().replace(/^#/, "");
652
+ const full = h.length === 3
653
+ ? h
653
654
  .split("")
654
655
  .map((c) => c + c)
655
656
  .join("")
656
- : hex;
657
+ : h;
657
658
  if (!/^[0-9a-fA-F]{6}$/.test(full)) {
658
- return DARK_INK;
659
+ return null;
659
660
  }
660
- const channel = (i) => {
661
- const c = parseInt(full.slice(i, i + 2), 16) / 255;
662
- return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
661
+ return [
662
+ parseInt(full.slice(0, 2), 16),
663
+ parseInt(full.slice(2, 4), 16),
664
+ parseInt(full.slice(4, 6), 16),
665
+ ];
666
+ }
667
+ /** WCAG relative luminance of a hex colour, or `null` if it isn't a hex. */
668
+ function foldLuminance(hex) {
669
+ const rgb = parseHex(hex);
670
+ if (!rgb) {
671
+ return null;
672
+ }
673
+ const lin = (c) => {
674
+ const s = c / 255;
675
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
663
676
  };
664
- const luminance = 0.2126 * channel(0) + 0.7152 * channel(2) + 0.0722 * channel(4);
665
- return luminance > 0.4 ? DARK_INK : LIGHT_INK;
677
+ return 0.2126 * lin(rgb[0]) + 0.7152 * lin(rgb[1]) + 0.0722 * lin(rgb[2]);
678
+ }
679
+ /** WCAG contrast ratio between two hex colours (1–21), or `null` if either
680
+ * isn't a hex. */
681
+ function foldContrast(a, b) {
682
+ const la = foldLuminance(a);
683
+ const lb = foldLuminance(b);
684
+ if (la === null || lb === null) {
685
+ return null;
686
+ }
687
+ const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
688
+ return (hi + 0.05) / (lo + 0.05);
666
689
  }
690
+ /**
691
+ * The **more legible** of two inks for text drawn on `fill` — whichever has the
692
+ * higher WCAG contrast. Picking the max (rather than thresholding luminance)
693
+ * guarantees the best of the pair for *any* fill: at the black/white crossover
694
+ * both inks already clear ~4.5:1, so every fill lands at or above it. A non-hex
695
+ * `fill` falls back to `dark`.
696
+ */
697
+ function foldReadableInk(fill, dark, light) {
698
+ const withDark = foldContrast(fill, dark);
699
+ const withLight = foldContrast(fill, light);
700
+ if (withDark === null || withLight === null) {
701
+ return dark;
702
+ }
703
+ return withDark >= withLight ? dark : light;
704
+ }
705
+
706
+ /** Ink for text on a light vs dark categorical fill. */
707
+ const DARK_INK = "#1a202c";
708
+ const LIGHT_INK = "#ffffff";
667
709
  /**
668
710
  * `<fold-avatar>` — a user/entity avatar with initials or an image.
669
711
  *
@@ -672,11 +714,17 @@ function readableInk(fill) {
672
714
  * (so the same seed is the same colour everywhere, and one `registry.use(...)`
673
715
  * recolours every avatar).
674
716
  *
717
+ * A broken `imageUrl` falls back to the initials rather than the browser's
718
+ * broken-image glyph (and retries when the URL changes).
719
+ *
675
720
  * Three orthogonal state axes compose: `variant` (fill), `muted` (presence —
676
721
  * dim for an absence/inactive person), and `ring` + `ringStyle` (a status
677
722
  * outline). The component stays domain-agnostic: a screen maps its own states
678
723
  * (e.g. absent → `[muted]`, a scheduled arrival → `ring="accent"`
679
- * `ringStyle="dotted"`), the avatar just draws the primitives.
724
+ * `ringStyle="dotted"`), the avatar just draws the primitives. The ring is a
725
+ * redundant emphasis cue — never the sole carrier of a state — because on a
726
+ * light page a soft status hue (amber/green) can't clear WCAG 3:1 as a thin
727
+ * line (status-ring-contrast.spec.ts).
680
728
  *
681
729
  * ## Inputs
682
730
  *
@@ -687,12 +735,19 @@ function readableInk(fill) {
687
735
  * | `colorSeed` | `string` | `name` | String used to pick the deterministic colour. |
688
736
  * | `variant` | `'solid' \| 'ghost'` | `'solid'` | `'ghost'` renders a dashed border (for guests). |
689
737
  * | `square` | `boolean` | `false` | Square shape with a small radius (for orgs). |
690
- * | `imageUrl` | `string` | — | Image/logo URL. Replaces initials when set. |
738
+ * | `imageUrl` | `string` | — | Image/logo URL. Replaces initials when set; falls back to initials if it fails to load. |
691
739
  * | `muted` | `boolean` | `false` | Dim the avatar (same hue, less intense) — absence / inactive. |
692
740
  * | `ring` | `FoldAvatarRing` | `'none'` | A status outline (`accent`/`info`/`warning`/`alert`/`success`). |
693
741
  * | `ringStyle` | `'solid' \| 'dotted'` | `'solid'` | Ring line style — `dotted` for scheduled/tentative states. |
694
742
  *
695
743
  * @selector `fold-avatar`
744
+ *
745
+ * @example
746
+ * ```html
747
+ * <fold-avatar name="Clément Aubry" size="lg" />
748
+ * <fold-avatar name="Léa Petit" ring="accent" ringStyle="dotted" />
749
+ * <fold-avatar name="Acme Corp" square imageUrl="/logo.svg" />
750
+ * ```
696
751
  */
697
752
  class FoldAvatarComponent {
698
753
  palette = inject(FoldPaletteRegistry);
@@ -715,8 +770,20 @@ class FoldAvatarComponent {
715
770
  /** Ring line style — `'dotted'` for scheduled / tentative states. */
716
771
  ringStyle = input("solid", /* @ts-ignore */
717
772
  ...(ngDevMode ? [{ debugName: "ringStyle" }] : /* istanbul ignore next */ []));
718
- /** Readable ink for the initials, derived from the fill's luminance. */
719
- onColor = computed(() => readableInk(this.color()), /* @ts-ignore */
773
+ /** True once the `<img>` fails to load. Resets whenever `imageUrl` changes,
774
+ * so a corrected URL is retried rather than staying on the fallback. */
775
+ imageFailed = linkedSignal(() => {
776
+ this.imageUrl();
777
+ return false;
778
+ }, /* @ts-ignore */
779
+ ...(ngDevMode ? [{ debugName: "imageFailed" }] : /* istanbul ignore next */ []));
780
+ /** Show the image only when a URL is set AND it has not failed — a broken URL
781
+ * falls back to the initials instead of the browser's broken-image glyph. */
782
+ showImage = computed(() => Boolean(this.imageUrl()) && !this.imageFailed(), /* @ts-ignore */
783
+ ...(ngDevMode ? [{ debugName: "showImage" }] : /* istanbul ignore next */ []));
784
+ /** Readable ink for the initials — the higher-contrast of dark/light ink on
785
+ * the fill, so a custom palette stays legible. */
786
+ onColor = computed(() => foldReadableInk(this.color(), DARK_INK, LIGHT_INK), /* @ts-ignore */
720
787
  ...(ngDevMode ? [{ debugName: "onColor" }] : /* istanbul ignore next */ []));
721
788
  initials = computed(() => {
722
789
  const n = this.name().trim();
@@ -734,11 +801,11 @@ class FoldAvatarComponent {
734
801
  color = computed(() => this.palette.colorFor(this.colorSeed() ?? this.name()), /* @ts-ignore */
735
802
  ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
736
803
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
737
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldAvatarComponent, isStandalone: true, selector: "fold-avatar", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, colorSeed: { classPropertyName: "colorSeed", publicName: "colorSeed", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, square: { classPropertyName: "square", publicName: "square", isSignal: true, isRequired: false, transformFunction: null }, imageUrl: { classPropertyName: "imageUrl", publicName: "imageUrl", isSignal: true, isRequired: false, transformFunction: null }, muted: { classPropertyName: "muted", publicName: "muted", isSignal: true, isRequired: false, transformFunction: null }, ring: { classPropertyName: "ring", publicName: "ring", isSignal: true, isRequired: false, transformFunction: null }, ringStyle: { classPropertyName: "ringStyle", publicName: "ringStyle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (imageUrl()) {\n <div\n class=\"avatar has-image\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [attr.title]=\"name()\"\n >\n <img [src]=\"imageUrl()\" [alt]=\"name()\" class=\"avatar-img\" />\n </div>\n} @else {\n <div\n class=\"avatar\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [style.background]=\"variant() === 'solid' ? color() : ''\"\n [style.color]=\"variant() === 'solid' ? onColor() : ''\"\n [attr.title]=\"name()\"\n >\n {{ initials() }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.avatar{display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-radius:var(--fold-radius-round)}.avatar.size-sm{width:20px;height:20px;font-size:7px}.avatar.size-md{width:32px;height:32px;font-size:11px}.avatar.size-lg{width:44px;height:44px;font-size:15px}.avatar.variant-ghost{background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);border:1px dashed var(--fold-color-border)}.avatar.shape-square{border-radius:var(--fold-radius-sm)}.avatar.has-image{overflow:hidden;background:var(--fold-color-surface-raised);border:1px solid var(--fold-color-border)}.avatar-img{width:100%;height:100%;object-fit:cover}.avatar.is-muted{opacity:.45}.avatar[data-ring]{outline:2px solid var(--avatar-ring, transparent);outline-offset:2px}.avatar[data-ring-style=dotted]{outline-style:dotted}.avatar[data-ring=accent]{--avatar-ring: var(--fold-color-primary)}.avatar[data-ring=info]{--avatar-ring: var(--fold-color-info)}.avatar[data-ring=warning]{--avatar-ring: var(--fold-color-warning)}.avatar[data-ring=alert]{--avatar-ring: var(--fold-color-alert)}.avatar[data-ring=success]{--avatar-ring: var(--fold-color-success)}\n"] });
804
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldAvatarComponent, isStandalone: true, selector: "fold-avatar", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, colorSeed: { classPropertyName: "colorSeed", publicName: "colorSeed", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, square: { classPropertyName: "square", publicName: "square", isSignal: true, isRequired: false, transformFunction: null }, imageUrl: { classPropertyName: "imageUrl", publicName: "imageUrl", isSignal: true, isRequired: false, transformFunction: null }, muted: { classPropertyName: "muted", publicName: "muted", isSignal: true, isRequired: false, transformFunction: null }, ring: { classPropertyName: "ring", publicName: "ring", isSignal: true, isRequired: false, transformFunction: null }, ringStyle: { classPropertyName: "ringStyle", publicName: "ringStyle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (showImage()) {\n <div\n class=\"avatar has-image\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [attr.title]=\"name()\"\n >\n <img\n [src]=\"imageUrl()\"\n [alt]=\"name()\"\n class=\"avatar-img\"\n (error)=\"imageFailed.set(true)\"\n />\n </div>\n} @else {\n <div\n class=\"avatar\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [style.background]=\"variant() === 'solid' ? color() : ''\"\n [style.color]=\"variant() === 'solid' ? onColor() : ''\"\n [attr.title]=\"name()\"\n >\n {{ initials() }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.avatar{display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-radius:var(--fold-radius-round)}.avatar.size-sm{width:20px;height:20px;font-size:7px}.avatar.size-md{width:32px;height:32px;font-size:11px}.avatar.size-lg{width:44px;height:44px;font-size:15px}.avatar.variant-ghost{background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);border:1px dashed var(--fold-color-border)}.avatar.shape-square{border-radius:var(--fold-radius-sm)}.avatar.has-image{overflow:hidden;background:var(--fold-color-surface-raised);border:1px solid var(--fold-color-border)}.avatar.has-image.variant-ghost{border-style:dashed}.avatar-img{width:100%;height:100%;object-fit:cover}.avatar.is-muted{opacity:.45}.avatar[data-ring]{outline:2px solid var(--avatar-ring, transparent);outline-offset:2px}.avatar[data-ring-style=dotted]{outline-style:dotted}.avatar[data-ring=accent]{--avatar-ring: var(--fold-color-primary)}.avatar[data-ring=info]{--avatar-ring: var(--fold-color-info)}.avatar[data-ring=warning]{--avatar-ring: var(--fold-color-warning)}.avatar[data-ring=alert]{--avatar-ring: var(--fold-color-alert)}.avatar[data-ring=success]{--avatar-ring: var(--fold-color-success)}\n"] });
738
805
  }
739
806
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAvatarComponent, decorators: [{
740
807
  type: Component,
741
- args: [{ selector: "fold-avatar", standalone: true, template: "@if (imageUrl()) {\n <div\n class=\"avatar has-image\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [attr.title]=\"name()\"\n >\n <img [src]=\"imageUrl()\" [alt]=\"name()\" class=\"avatar-img\" />\n </div>\n} @else {\n <div\n class=\"avatar\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [style.background]=\"variant() === 'solid' ? color() : ''\"\n [style.color]=\"variant() === 'solid' ? onColor() : ''\"\n [attr.title]=\"name()\"\n >\n {{ initials() }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.avatar{display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-radius:var(--fold-radius-round)}.avatar.size-sm{width:20px;height:20px;font-size:7px}.avatar.size-md{width:32px;height:32px;font-size:11px}.avatar.size-lg{width:44px;height:44px;font-size:15px}.avatar.variant-ghost{background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);border:1px dashed var(--fold-color-border)}.avatar.shape-square{border-radius:var(--fold-radius-sm)}.avatar.has-image{overflow:hidden;background:var(--fold-color-surface-raised);border:1px solid var(--fold-color-border)}.avatar-img{width:100%;height:100%;object-fit:cover}.avatar.is-muted{opacity:.45}.avatar[data-ring]{outline:2px solid var(--avatar-ring, transparent);outline-offset:2px}.avatar[data-ring-style=dotted]{outline-style:dotted}.avatar[data-ring=accent]{--avatar-ring: var(--fold-color-primary)}.avatar[data-ring=info]{--avatar-ring: var(--fold-color-info)}.avatar[data-ring=warning]{--avatar-ring: var(--fold-color-warning)}.avatar[data-ring=alert]{--avatar-ring: var(--fold-color-alert)}.avatar[data-ring=success]{--avatar-ring: var(--fold-color-success)}\n"] }]
808
+ args: [{ selector: "fold-avatar", standalone: true, template: "@if (showImage()) {\n <div\n class=\"avatar has-image\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [attr.title]=\"name()\"\n >\n <img\n [src]=\"imageUrl()\"\n [alt]=\"name()\"\n class=\"avatar-img\"\n (error)=\"imageFailed.set(true)\"\n />\n </div>\n} @else {\n <div\n class=\"avatar\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [style.background]=\"variant() === 'solid' ? color() : ''\"\n [style.color]=\"variant() === 'solid' ? onColor() : ''\"\n [attr.title]=\"name()\"\n >\n {{ initials() }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.avatar{display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-radius:var(--fold-radius-round)}.avatar.size-sm{width:20px;height:20px;font-size:7px}.avatar.size-md{width:32px;height:32px;font-size:11px}.avatar.size-lg{width:44px;height:44px;font-size:15px}.avatar.variant-ghost{background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);border:1px dashed var(--fold-color-border)}.avatar.shape-square{border-radius:var(--fold-radius-sm)}.avatar.has-image{overflow:hidden;background:var(--fold-color-surface-raised);border:1px solid var(--fold-color-border)}.avatar.has-image.variant-ghost{border-style:dashed}.avatar-img{width:100%;height:100%;object-fit:cover}.avatar.is-muted{opacity:.45}.avatar[data-ring]{outline:2px solid var(--avatar-ring, transparent);outline-offset:2px}.avatar[data-ring-style=dotted]{outline-style:dotted}.avatar[data-ring=accent]{--avatar-ring: var(--fold-color-primary)}.avatar[data-ring=info]{--avatar-ring: var(--fold-color-info)}.avatar[data-ring=warning]{--avatar-ring: var(--fold-color-warning)}.avatar[data-ring=alert]{--avatar-ring: var(--fold-color-alert)}.avatar[data-ring=success]{--avatar-ring: var(--fold-color-success)}\n"] }]
742
809
  }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], colorSeed: [{ type: i0.Input, args: [{ isSignal: true, alias: "colorSeed", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], square: [{ type: i0.Input, args: [{ isSignal: true, alias: "square", required: false }] }], imageUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUrl", required: false }] }], muted: [{ type: i0.Input, args: [{ isSignal: true, alias: "muted", required: false }] }], ring: [{ type: i0.Input, args: [{ isSignal: true, alias: "ring", required: false }] }], ringStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "ringStyle", required: false }] }] } });
743
810
 
744
811
  /**
@@ -766,6 +833,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
766
833
  * | `ringStyle` | `'solid' \| 'dotted'` | `'solid'` | Ring line style (`dotted` = scheduled). |
767
834
  *
768
835
  * @selector `fold-avatar-detail`
836
+ *
837
+ * @example
838
+ * ```html
839
+ * <fold-avatar-detail primary="Alex Rivers" secondary="alex@sh3pherd.dev" />
840
+ * <fold-avatar-detail primary="Acme Corp" secondary="Organisation" square />
841
+ * ```
769
842
  */
770
843
  class FoldAvatarDetailComponent {
771
844
  primary = input.required(/* @ts-ignore */
@@ -1868,14 +1941,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1868
1941
  *
1869
1942
  * - `surface` — `card` (the raised card tint, default) · `sunken` (a deeper
1870
1943
  * surface for large containers, below the card — the ref's second card tint;
1871
- * pairs with a fainter `border-subtle` hairline).
1944
+ * pairs with a fainter `border-subtle` hairline) · `accent` (an accent-filled
1945
+ * card whose content sub-tree auto-inverts to a compatible on-accent palette).
1872
1946
  * - `radius` — `lg` (14px, default) · `md` · `sm`.
1873
1947
  * - `padding` — `md` (16px, default) · `none` · `sm` · `lg`. Sets the *body*
1874
1948
  * padding; override with a custom value via `--fold-card-padding`.
1875
- * - `interactive` — adds a hover lift (for clickable cards).
1876
- * - `separators` — draw a hairline between a projected header/footer and the body.
1877
- * - `raisedBands` — tint the header/footer bands a step above the surface
1878
- * (fainter on `sunken`).
1949
+ * - `interactive` — makes the whole card a clickable control (see below).
1950
+ * - `separators` — which bands get a hairline against the body:
1951
+ * `none` (default) · `header` · `footer` · `both`.
1952
+ * - `raisedBands` — which bands are tinted a step above the surface (fainter on
1953
+ * `sunken`): `none` (default) · `header` · `footer` · `both`.
1954
+ *
1955
+ * `separators` and `raisedBands` are per-band and independent axes, so a card can
1956
+ * have (say) a raised, un-separated header over a flush footer.
1879
1957
  *
1880
1958
  * Content projection:
1881
1959
  * - default slot → the card body.
@@ -1887,10 +1965,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1887
1965
  * never pads. So the content padding is identical whether or not the bands show —
1888
1966
  * toggling a header/footer never shifts the body.
1889
1967
  *
1968
+ * **Interactive cards.** `interactive` turns the whole card into a real control:
1969
+ * the host gains `role="button"`, `tabindex="0"`, a focus ring and a hover lift,
1970
+ * and Enter / Space (or a click) emit `(activated)`. Give it an accessible name
1971
+ * with `ariaLabel` when the card's text isn't a sufficient label.
1972
+ * A `role="button"` must not wrap other interactive controls — so an interactive
1973
+ * card must NOT contain buttons/links. For a card with its own inner actions,
1974
+ * keep it non-interactive and put a single primary `<a>`/`<button>` inside.
1975
+ *
1890
1976
  * ```html
1891
1977
  * <fold-card>Static content</fold-card>
1892
1978
  * <fold-card surface="sunken" padding="lg">Deep container</fold-card>
1893
- * <fold-card separators>
1979
+ * <fold-card separators="both">
1894
1980
  * <h3 cardHeader>Title</h3>
1895
1981
  * Body content
1896
1982
  * <div cardFooter>Actions</div>
@@ -1902,9 +1988,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1902
1988
  * (default `clip`) lets content escape the rounded corners.
1903
1989
  *
1904
1990
  * @selector `fold-card`
1991
+ *
1992
+ * @example
1993
+ * ```html
1994
+ * <fold-card interactive ariaLabel="Open Acme Records" (activated)="open()">
1995
+ * <h3>Acme Records</h3>
1996
+ * <p>128 contracts · 96 active</p>
1997
+ * </fold-card>
1998
+ * ```
1905
1999
  */
1906
2000
  class FoldCardComponent {
1907
- /** Surface tint — `card` (raised, default) or `sunken` (deeper container). */
2001
+ /** Surface tint — `card` (raised, default), `sunken` (deeper container), or
2002
+ * `accent` (an **accent-filled** card: the accent colour as ground, with the
2003
+ * whole content sub-tree auto-inverted to a compatible on-accent role-set —
2004
+ * text, borders, band gradation, and even nested buttons/links/icon-tiles
2005
+ * convert automatically). Use it to make a card in a grid stand out. */
1908
2006
  surface = input("card", /* @ts-ignore */
1909
2007
  ...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
1910
2008
  /** Corner radius — `lg` (default), `md`, or `sm`. */
@@ -1914,31 +2012,67 @@ class FoldCardComponent {
1914
2012
  * set with the `--fold-card-padding` CSS variable. */
1915
2013
  padding = input("md", /* @ts-ignore */
1916
2014
  ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
1917
- /** Add a hover lift for clickable cards. */
1918
- interactive = input(false, /* @ts-ignore */
1919
- ...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
1920
- /** Draw a hairline between a projected header/footer and the body. */
1921
- separators = input(false, { ...(ngDevMode ? { debugName: "separators" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1922
- /** Lift the header/footer bands with a subtle raised tint over the card
1923
- * surface (fainter on `sunken`), to set the chrome apart from the body. */
1924
- raisedBands = input(false, { ...(ngDevMode ? { debugName: "raisedBands" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2015
+ /** Make the whole card a clickable control: `role="button"` + `tabindex`, a
2016
+ * focus ring, a hover lift, and Enter/Space/click → `(activated)`. Must not
2017
+ * wrap other interactive controls (see the class docs). */
2018
+ interactive = input(false, { ...(ngDevMode ? { debugName: "interactive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2019
+ /** Accessible name for the interactive card, when its content isn't enough. */
2020
+ ariaLabel = input(/* @ts-ignore */
2021
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
2022
+ /** Which bands get a hairline against the body — `none` (default), `header`,
2023
+ * `footer`, or `both`. */
2024
+ separators = input("none", /* @ts-ignore */
2025
+ ...(ngDevMode ? [{ debugName: "separators" }] : /* istanbul ignore next */ []));
2026
+ /** Which bands are lifted with a subtle raised tint over the card surface
2027
+ * (fainter on `sunken`) — `none` (default), `header`, `footer`, or `both`. */
2028
+ raisedBands = input("none", /* @ts-ignore */
2029
+ ...(ngDevMode ? [{ debugName: "raisedBands" }] : /* istanbul ignore next */ []));
2030
+ /** Fires when an `interactive` card is activated (click, Enter, or Space). */
2031
+ activated = output();
2032
+ /** Whether a per-band chrome value applies to the given band. */
2033
+ hasBand(value, band) {
2034
+ return value === band || value === "both";
2035
+ }
2036
+ onActivate(event) {
2037
+ if (this.interactive()) {
2038
+ this.activated.emit(event);
2039
+ }
2040
+ }
2041
+ /** Enter/Space activate the card, matching the native button keyboard contract
2042
+ * (Space is prevented from scrolling the page). */
2043
+ onKeydown(event) {
2044
+ if (!this.interactive() || (event.key !== "Enter" && event.key !== " ")) {
2045
+ return;
2046
+ }
2047
+ event.preventDefault();
2048
+ this.activated.emit(event);
2049
+ }
1925
2050
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1926
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldCardComponent, isStandalone: true, selector: "fold-card", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, radius: { classPropertyName: "radius", publicName: "radius", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, raisedBands: { classPropertyName: "raisedBands", publicName: "raisedBands", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.s-sunken": "surface() === 'sunken'", "class.r-sm": "radius() === 'sm'", "class.r-md": "radius() === 'md'", "class.p-none": "padding() === 'none'", "class.p-sm": "padding() === 'sm'", "class.p-lg": "padding() === 'lg'", "class.is-interactive": "interactive()", "class.has-sep": "separators()", "class.raised-bands": "raisedBands()" } }, ngImport: i0, template: "<header class=\"card-header\"><ng-content select=\"[cardHeader]\" /></header>\n<div class=\"card-body\"><ng-content /></div>\n<footer class=\"card-footer\"><ng-content select=\"[cardFooter]\" /></footer>\n", styles: ["@charset \"UTF-8\";:host{--_pad: 16px;--_chrome: 12px 16px;--_radius: var(--fold-radius-lg);--_band-raise: var(--fold-color-surface-hover);--_sep-line: var(--fold-color-border-subtle);display:flex;flex-direction:column;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--_radius);overflow:var(--fold-card-overflow, clip)}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle);--_band-raise: var(--fold-color-surface-card)}:host(.r-md){--_radius: var(--fold-radius-md)}:host(.r-sm){--_radius: var(--fold-radius-sm)}:host(.p-none){--_pad: 0}:host(.p-sm){--_pad: 10px}:host(.p-lg){--_pad: 20px}.card-header,.card-footer{position:relative;padding:var(--_chrome)}.card-header:empty,.card-footer:empty{display:none}.card-header{border-radius:var(--_radius) var(--_radius) 0 0}.card-footer{border-radius:0 0 var(--_radius) var(--_radius)}.card-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:var(--fold-card-padding, var(--_pad))}:host(.raised-bands) .card-header:not(:empty),:host(.raised-bands) .card-footer:not(:empty){background-color:var(--_band-raise)}:host(.has-sep) .card-header:not(:empty){border-bottom:1px solid var(--_sep-line)}:host(.has-sep) .card-footer:not(:empty){border-top:1px solid var(--_sep-line)}:host(.is-interactive){cursor:pointer;transition:transform .18s ease,box-shadow .18s ease,border-color .1s ease}:host(.is-interactive:hover){transform:translateY(-2px);box-shadow:var(--fold-shadow-md)}\n"] });
2051
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldCardComponent, isStandalone: true, selector: "fold-card", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, radius: { classPropertyName: "radius", publicName: "radius", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, raisedBands: { classPropertyName: "raisedBands", publicName: "raisedBands", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activated: "activated" }, host: { listeners: { "click": "onActivate($event)", "keydown": "onKeydown($event)" }, properties: { "class.s-sunken": "surface() === 'sunken'", "class.s-accent": "surface() === 'accent'", "attr.data-surface": "surface() === 'accent' ? 'accent' : null", "class.r-sm": "radius() === 'sm'", "class.r-md": "radius() === 'md'", "class.p-none": "padding() === 'none'", "class.p-sm": "padding() === 'sm'", "class.p-lg": "padding() === 'lg'", "class.is-interactive": "interactive()", "class.sep-header": "hasBand(separators(), 'header')", "class.sep-footer": "hasBand(separators(), 'footer')", "class.raise-header": "hasBand(raisedBands(), 'header')", "class.raise-footer": "hasBand(raisedBands(), 'footer')", "attr.role": "interactive() ? 'button' : null", "attr.tabindex": "interactive() ? 0 : null", "attr.aria-label": "interactive() ? (ariaLabel() ?? null) : null" } }, ngImport: i0, template: "<div class=\"card-header\"><ng-content select=\"[cardHeader]\" /></div>\n<div class=\"card-body\"><ng-content /></div>\n<div class=\"card-footer\"><ng-content select=\"[cardFooter]\" /></div>\n", styles: ["@charset \"UTF-8\";:host{--_pad: 16px;--_chrome: 12px 16px;--_radius: var(--fold-radius-lg);--_band-raise: var(--fold-color-surface-hover);--_sep-line: var(--fold-color-border-subtle);display:flex;flex-direction:column;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--_radius);overflow:var(--fold-card-overflow, clip)}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle);--_band-raise: var(--fold-color-surface-card)}:host(.r-md){--_radius: var(--fold-radius-md)}:host(.r-sm){--_radius: var(--fold-radius-sm)}:host(.p-none){--_pad: 0}:host(.p-sm){--_pad: 10px}:host(.p-lg){--_pad: 20px}.card-header,.card-footer{position:relative;padding:var(--_chrome)}.card-header:empty,.card-footer:empty{display:none}.card-header{border-radius:var(--_radius) var(--_radius) 0 0}.card-footer{border-radius:0 0 var(--_radius) var(--_radius)}.card-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:var(--fold-card-padding, var(--_pad))}:host(.raise-header) .card-header:not(:empty),:host(.raise-footer) .card-footer:not(:empty){background-color:var(--_band-raise)}:host(.sep-header) .card-header:not(:empty){border-bottom:1px solid var(--_sep-line)}:host(.sep-footer) .card-footer:not(:empty){border-top:1px solid var(--_sep-line)}:host(.is-interactive){cursor:pointer;transition:transform .18s ease,box-shadow .18s ease}:host(.is-interactive:hover){transform:translateY(-2px);box-shadow:var(--fold-shadow-md)}:host(.is-interactive:focus-visible){outline:2px solid var(--fold-color-primary);outline-offset:2px}@media(prefers-reduced-motion:reduce){:host(.is-interactive){transition:none}}:host(.s-accent){background:var(--fold-color-primary);border-color:var(--fold-color-primary);color:var(--fold-color-on-primary);--_band-raise: color-mix( in srgb, var(--_accent-ink) 16%, var(--_accent-fill) );--_sep-line: color-mix(in srgb, var(--_accent-ink) 24%, transparent)}:host(.s-accent.is-interactive:focus-visible){outline-color:var(--fold-color-on-primary)}\n"] });
1927
2052
  }
1928
2053
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCardComponent, decorators: [{
1929
2054
  type: Component,
1930
2055
  args: [{ selector: "fold-card", standalone: true, host: {
1931
2056
  "[class.s-sunken]": "surface() === 'sunken'",
2057
+ "[class.s-accent]": "surface() === 'accent'",
2058
+ "[attr.data-surface]": "surface() === 'accent' ? 'accent' : null",
1932
2059
  "[class.r-sm]": "radius() === 'sm'",
1933
2060
  "[class.r-md]": "radius() === 'md'",
1934
2061
  "[class.p-none]": "padding() === 'none'",
1935
2062
  "[class.p-sm]": "padding() === 'sm'",
1936
2063
  "[class.p-lg]": "padding() === 'lg'",
1937
2064
  "[class.is-interactive]": "interactive()",
1938
- "[class.has-sep]": "separators()",
1939
- "[class.raised-bands]": "raisedBands()",
1940
- }, template: "<header class=\"card-header\"><ng-content select=\"[cardHeader]\" /></header>\n<div class=\"card-body\"><ng-content /></div>\n<footer class=\"card-footer\"><ng-content select=\"[cardFooter]\" /></footer>\n", styles: ["@charset \"UTF-8\";:host{--_pad: 16px;--_chrome: 12px 16px;--_radius: var(--fold-radius-lg);--_band-raise: var(--fold-color-surface-hover);--_sep-line: var(--fold-color-border-subtle);display:flex;flex-direction:column;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--_radius);overflow:var(--fold-card-overflow, clip)}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle);--_band-raise: var(--fold-color-surface-card)}:host(.r-md){--_radius: var(--fold-radius-md)}:host(.r-sm){--_radius: var(--fold-radius-sm)}:host(.p-none){--_pad: 0}:host(.p-sm){--_pad: 10px}:host(.p-lg){--_pad: 20px}.card-header,.card-footer{position:relative;padding:var(--_chrome)}.card-header:empty,.card-footer:empty{display:none}.card-header{border-radius:var(--_radius) var(--_radius) 0 0}.card-footer{border-radius:0 0 var(--_radius) var(--_radius)}.card-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:var(--fold-card-padding, var(--_pad))}:host(.raised-bands) .card-header:not(:empty),:host(.raised-bands) .card-footer:not(:empty){background-color:var(--_band-raise)}:host(.has-sep) .card-header:not(:empty){border-bottom:1px solid var(--_sep-line)}:host(.has-sep) .card-footer:not(:empty){border-top:1px solid var(--_sep-line)}:host(.is-interactive){cursor:pointer;transition:transform .18s ease,box-shadow .18s ease,border-color .1s ease}:host(.is-interactive:hover){transform:translateY(-2px);box-shadow:var(--fold-shadow-md)}\n"] }]
1941
- }], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], radius: [{ type: i0.Input, args: [{ isSignal: true, alias: "radius", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], raisedBands: [{ type: i0.Input, args: [{ isSignal: true, alias: "raisedBands", required: false }] }] } });
2065
+ "[class.sep-header]": "hasBand(separators(), 'header')",
2066
+ "[class.sep-footer]": "hasBand(separators(), 'footer')",
2067
+ "[class.raise-header]": "hasBand(raisedBands(), 'header')",
2068
+ "[class.raise-footer]": "hasBand(raisedBands(), 'footer')",
2069
+ "[attr.role]": "interactive() ? 'button' : null",
2070
+ "[attr.tabindex]": "interactive() ? 0 : null",
2071
+ "[attr.aria-label]": "interactive() ? (ariaLabel() ?? null) : null",
2072
+ "(click)": "onActivate($event)",
2073
+ "(keydown)": "onKeydown($event)",
2074
+ }, template: "<div class=\"card-header\"><ng-content select=\"[cardHeader]\" /></div>\n<div class=\"card-body\"><ng-content /></div>\n<div class=\"card-footer\"><ng-content select=\"[cardFooter]\" /></div>\n", styles: ["@charset \"UTF-8\";:host{--_pad: 16px;--_chrome: 12px 16px;--_radius: var(--fold-radius-lg);--_band-raise: var(--fold-color-surface-hover);--_sep-line: var(--fold-color-border-subtle);display:flex;flex-direction:column;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--_radius);overflow:var(--fold-card-overflow, clip)}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle);--_band-raise: var(--fold-color-surface-card)}:host(.r-md){--_radius: var(--fold-radius-md)}:host(.r-sm){--_radius: var(--fold-radius-sm)}:host(.p-none){--_pad: 0}:host(.p-sm){--_pad: 10px}:host(.p-lg){--_pad: 20px}.card-header,.card-footer{position:relative;padding:var(--_chrome)}.card-header:empty,.card-footer:empty{display:none}.card-header{border-radius:var(--_radius) var(--_radius) 0 0}.card-footer{border-radius:0 0 var(--_radius) var(--_radius)}.card-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:var(--fold-card-padding, var(--_pad))}:host(.raise-header) .card-header:not(:empty),:host(.raise-footer) .card-footer:not(:empty){background-color:var(--_band-raise)}:host(.sep-header) .card-header:not(:empty){border-bottom:1px solid var(--_sep-line)}:host(.sep-footer) .card-footer:not(:empty){border-top:1px solid var(--_sep-line)}:host(.is-interactive){cursor:pointer;transition:transform .18s ease,box-shadow .18s ease}:host(.is-interactive:hover){transform:translateY(-2px);box-shadow:var(--fold-shadow-md)}:host(.is-interactive:focus-visible){outline:2px solid var(--fold-color-primary);outline-offset:2px}@media(prefers-reduced-motion:reduce){:host(.is-interactive){transition:none}}:host(.s-accent){background:var(--fold-color-primary);border-color:var(--fold-color-primary);color:var(--fold-color-on-primary);--_band-raise: color-mix( in srgb, var(--_accent-ink) 16%, var(--_accent-fill) );--_sep-line: color-mix(in srgb, var(--_accent-ink) 24%, transparent)}:host(.s-accent.is-interactive:focus-visible){outline-color:var(--fold-color-on-primary)}\n"] }]
2075
+ }], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], radius: [{ type: i0.Input, args: [{ isSignal: true, alias: "radius", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], raisedBands: [{ type: i0.Input, args: [{ isSignal: true, alias: "raisedBands", required: false }] }], activated: [{ type: i0.Output, args: ["activated"] }] } });
1942
2076
 
1943
2077
  /**
1944
2078
  * `<fold-element-title>` — the label that heads a section, card or panel. Fully
@@ -2039,7 +2173,7 @@ class FoldContextCardComponent {
2039
2173
  subtitle = input(/* @ts-ignore */
2040
2174
  ...(ngDevMode ? [undefined, { debugName: "subtitle" }] : /* istanbul ignore next */ []));
2041
2175
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldContextCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2042
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldContextCardComponent, isStandalone: true, selector: "fold-context-card", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconTone: { classPropertyName: "iconTone", publicName: "iconTone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<fold-card padding=\"none\">\n <div class=\"cc-head\">\n <fold-element-title\n variant=\"title\"\n [level]=\"3\"\n [icon]=\"icon()\"\n [iconTone]=\"iconTone()\"\n [title]=\"title()\"\n [subtitle]=\"subtitle()\"\n />\n </div>\n <div class=\"cc-body\"><ng-content /></div>\n <div class=\"cc-foot\"><ng-content select=\"[footer]\" /></div>\n</fold-card>\n", styles: ["@charset \"UTF-8\";:host{display:block}.cc-head{padding:16px;border-bottom:1px solid var(--fold-color-border-subtle)}.cc-body{padding:2px 16px}.cc-foot{display:flex;justify-content:center;padding:14px 16px;border-top:1px solid var(--fold-color-border-subtle)}.cc-foot:empty{display:none}\n"], dependencies: [{ kind: "component", type: FoldCardComponent, selector: "fold-card", inputs: ["surface", "radius", "padding", "interactive", "separators", "raisedBands"] }, { kind: "component", type: FoldElementTitleComponent, selector: "fold-element-title", inputs: ["icon", "iconTone", "title", "subtitle", "variant", "level", "headingId"] }] });
2176
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldContextCardComponent, isStandalone: true, selector: "fold-context-card", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconTone: { classPropertyName: "iconTone", publicName: "iconTone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<fold-card padding=\"none\">\n <div class=\"cc-head\">\n <fold-element-title\n variant=\"title\"\n [level]=\"3\"\n [icon]=\"icon()\"\n [iconTone]=\"iconTone()\"\n [title]=\"title()\"\n [subtitle]=\"subtitle()\"\n />\n </div>\n <div class=\"cc-body\"><ng-content /></div>\n <div class=\"cc-foot\"><ng-content select=\"[footer]\" /></div>\n</fold-card>\n", styles: ["@charset \"UTF-8\";:host{display:block}.cc-head{padding:16px;border-bottom:1px solid var(--fold-color-border-subtle)}.cc-body{padding:2px 16px}.cc-foot{display:flex;justify-content:center;padding:14px 16px;border-top:1px solid var(--fold-color-border-subtle)}.cc-foot:empty{display:none}\n"], dependencies: [{ kind: "component", type: FoldCardComponent, selector: "fold-card", inputs: ["surface", "radius", "padding", "interactive", "ariaLabel", "separators", "raisedBands"], outputs: ["activated"] }, { kind: "component", type: FoldElementTitleComponent, selector: "fold-element-title", inputs: ["icon", "iconTone", "title", "subtitle", "variant", "level", "headingId"] }] });
2043
2177
  }
2044
2178
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldContextCardComponent, decorators: [{
2045
2179
  type: Component,
@@ -2288,6 +2422,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2288
2422
  * - `tone` — `accent` (default, brand link) · `muted` (secondary).
2289
2423
  * - `icon` / `trailingIcon` — optional glyphs around the label.
2290
2424
  * - `disabled` — button mode only.
2425
+ * - `target` / `rel` — anchor mode only. Setting `target="_blank"` auto-applies
2426
+ * `rel="noopener noreferrer"` (safe by default); override with an explicit
2427
+ * `rel`.
2291
2428
  *
2292
2429
  * @selector `fold-link`
2293
2430
  *
@@ -2296,7 +2433,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2296
2433
  * <fold-link icon="company" trailingIcon="chevron-right" (clicked)="openOrg()">
2297
2434
  * Voir l'organigramme
2298
2435
  * </fold-link>
2299
- * <fold-link href="https://sh3pherd.dev/docs" tone="muted">Documentation</fold-link>
2436
+ * <fold-link href="https://sh3pherd.dev/docs" target="_blank" tone="muted">
2437
+ * Documentation
2438
+ * </fold-link>
2300
2439
  * ```
2301
2440
  */
2302
2441
  class FoldLinkComponent {
@@ -2312,17 +2451,27 @@ class FoldLinkComponent {
2312
2451
  /** When set, the link renders as an `<a href>` instead of a button. */
2313
2452
  href = input(/* @ts-ignore */
2314
2453
  ...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
2454
+ /** Anchor target (e.g. `_blank`). Anchor mode only. */
2455
+ target = input(/* @ts-ignore */
2456
+ ...(ngDevMode ? [undefined, { debugName: "target" }] : /* istanbul ignore next */ []));
2457
+ /** Anchor `rel`. Anchor mode only. Defaults to `noopener noreferrer` when
2458
+ * `target="_blank"`; set explicitly to override. */
2459
+ rel = input(/* @ts-ignore */
2460
+ ...(ngDevMode ? [undefined, { debugName: "rel" }] : /* istanbul ignore next */ []));
2315
2461
  /** Disable the button form (no effect on the `href` form). */
2316
2462
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2317
- /** Fires on click in the button form. */
2463
+ /** Fires on click in the button form; carries the `MouseEvent`. */
2318
2464
  clicked = output();
2465
+ /** Resolved `rel` — the explicit value, or a safe default for a new tab. */
2466
+ resolvedRel = computed(() => this.rel() ?? (this.target() === "_blank" ? "noopener noreferrer" : null), /* @ts-ignore */
2467
+ ...(ngDevMode ? [{ debugName: "resolvedRel" }] : /* istanbul ignore next */ []));
2319
2468
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2320
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldLinkComponent, isStandalone: true, selector: "fold-link", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, trailingIcon: { classPropertyName: "trailingIcon", publicName: "trailingIcon", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, host: { properties: { "class.tone-muted": "tone() === 'muted'" } }, ngImport: i0, template: "<ng-template #inner>\n @if (icon(); as i) {\n <fold-icon [name]=\"i\" size=\"sm\" />\n }\n <span class=\"lnk-label\"><ng-content /></span>\n @if (trailingIcon(); as t) {\n <fold-icon [name]=\"t\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as h) {\n <a class=\"lnk\" [href]=\"h\">\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"lnk\"\n [disabled]=\"disabled()\"\n (click)=\"clicked.emit()\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </button>\n}\n", styles: [":host{display:inline-flex;min-width:0}.lnk{display:inline-flex;align-items:center;gap:6px;min-width:0;padding:0;border:0;background:none;font-family:inherit;font-size:var(--fold-text-xs);font-weight:600;color:var(--fold-color-primary);text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}:host(.tone-muted) .lnk{color:var(--fold-color-text-secondary)}.lnk:hover .lnk-label{text-decoration:underline}.lnk:disabled{color:var(--fold-color-text-muted);cursor:not-allowed}.lnk:disabled .lnk-label{text-decoration:none}.lnk-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }] });
2469
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldLinkComponent, isStandalone: true, selector: "fold-link", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, trailingIcon: { classPropertyName: "trailingIcon", publicName: "trailingIcon", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, host: { properties: { "class.tone-muted": "tone() === 'muted'" } }, ngImport: i0, template: "<ng-template #inner>\n @if (icon(); as i) {\n <fold-icon [name]=\"i\" size=\"sm\" />\n }\n <span class=\"lnk-label\"><ng-content /></span>\n @if (trailingIcon(); as t) {\n <fold-icon [name]=\"t\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as h) {\n <a\n class=\"lnk\"\n [href]=\"h\"\n [attr.target]=\"target() ?? null\"\n [attr.rel]=\"resolvedRel()\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"lnk\"\n [disabled]=\"disabled()\"\n (click)=\"clicked.emit($event)\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </button>\n}\n", styles: [":host{display:inline-flex;min-width:0}.lnk{display:inline-flex;align-items:center;gap:6px;min-width:0;padding:0;border:0;background:none;font-family:inherit;font-size:var(--fold-text-xs);font-weight:600;color:var(--fold-color-primary);text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}:host(.tone-muted) .lnk{color:var(--fold-color-text-secondary)}.lnk:hover .lnk-label{text-decoration:underline}.lnk:disabled{color:var(--fold-color-text-muted);cursor:not-allowed}.lnk:disabled .lnk-label{text-decoration:none}.lnk-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }] });
2321
2470
  }
2322
2471
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldLinkComponent, decorators: [{
2323
2472
  type: Component,
2324
- args: [{ selector: "fold-link", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], host: { "[class.tone-muted]": "tone() === 'muted'" }, template: "<ng-template #inner>\n @if (icon(); as i) {\n <fold-icon [name]=\"i\" size=\"sm\" />\n }\n <span class=\"lnk-label\"><ng-content /></span>\n @if (trailingIcon(); as t) {\n <fold-icon [name]=\"t\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as h) {\n <a class=\"lnk\" [href]=\"h\">\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"lnk\"\n [disabled]=\"disabled()\"\n (click)=\"clicked.emit()\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </button>\n}\n", styles: [":host{display:inline-flex;min-width:0}.lnk{display:inline-flex;align-items:center;gap:6px;min-width:0;padding:0;border:0;background:none;font-family:inherit;font-size:var(--fold-text-xs);font-weight:600;color:var(--fold-color-primary);text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}:host(.tone-muted) .lnk{color:var(--fold-color-text-secondary)}.lnk:hover .lnk-label{text-decoration:underline}.lnk:disabled{color:var(--fold-color-text-muted);cursor:not-allowed}.lnk:disabled .lnk-label{text-decoration:none}.lnk-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"] }]
2325
- }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], trailingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingIcon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
2473
+ args: [{ selector: "fold-link", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], host: { "[class.tone-muted]": "tone() === 'muted'" }, template: "<ng-template #inner>\n @if (icon(); as i) {\n <fold-icon [name]=\"i\" size=\"sm\" />\n }\n <span class=\"lnk-label\"><ng-content /></span>\n @if (trailingIcon(); as t) {\n <fold-icon [name]=\"t\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as h) {\n <a\n class=\"lnk\"\n [href]=\"h\"\n [attr.target]=\"target() ?? null\"\n [attr.rel]=\"resolvedRel()\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"lnk\"\n [disabled]=\"disabled()\"\n (click)=\"clicked.emit($event)\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </button>\n}\n", styles: [":host{display:inline-flex;min-width:0}.lnk{display:inline-flex;align-items:center;gap:6px;min-width:0;padding:0;border:0;background:none;font-family:inherit;font-size:var(--fold-text-xs);font-weight:600;color:var(--fold-color-primary);text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}:host(.tone-muted) .lnk{color:var(--fold-color-text-secondary)}.lnk:hover .lnk-label{text-decoration:underline}.lnk:disabled{color:var(--fold-color-text-muted);cursor:not-allowed}.lnk:disabled .lnk-label{text-decoration:none}.lnk-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"] }]
2474
+ }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], trailingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingIcon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], rel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
2326
2475
 
2327
2476
  /**
2328
2477
  * `<fold-menu>` — a vertical icon navigation rail (the app's primary menu shell).
@@ -2866,13 +3015,15 @@ class FoldHeroCardComponent {
2866
3015
  /** Add a primary accent bar down the left edge (composable with any surface). */
2867
3016
  accentBar = input(false, { ...(ngDevMode ? { debugName: "accentBar" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2868
3017
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldHeroCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2869
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldHeroCardComponent, isStandalone: true, selector: "fold-hero-card", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, accentBar: { classPropertyName: "accentBar", publicName: "accentBar", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.s-sunken": "surface() === 'sunken'", "class.s-primary": "surface() === 'primary'", "class.a-subtle": "accent() === 'subtle'", "class.a-gradient": "accent() === 'gradient'", "class.has-bar": "accentBar()", "class.p-sm": "padding() === 'sm'", "class.p-md": "padding() === 'md'" } }, ngImport: i0, template: "<ng-content />\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;overflow:hidden;isolation:isolate;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-lg);padding:20px}:host(.p-sm){padding:10px}:host(.p-md){padding:16px}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle)}:host(.s-primary){background:var(--fold-color-primary);border-color:var(--fold-color-primary);color:var(--fold-color-on-primary)}:host(.a-subtle):after,:host(.a-gradient):after{content:\"\";position:absolute;inset:0;z-index:-1;pointer-events:none}:host(.a-subtle):after{background:radial-gradient(120% 140% at 100% 0%,color-mix(in srgb,var(--fold-color-primary) 10%,transparent),transparent 45%),linear-gradient(160deg,transparent 30%,color-mix(in srgb,var(--fold-color-surface-sunken) 50%,transparent))}:host(.a-gradient){border-color:var(--fold-color-primary-border)}:host(.a-gradient):after{background:radial-gradient(260px circle at 90% -10%,color-mix(in srgb,var(--fold-color-primary) 22%,transparent),transparent 68%),linear-gradient(180deg,color-mix(in srgb,var(--fold-color-primary) 8%,transparent),transparent 60%)}:host(.has-bar):before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(var(--fold-color-primary),color-mix(in srgb,var(--fold-color-primary) 30%,transparent))}\n"] });
3018
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldHeroCardComponent, isStandalone: true, selector: "fold-hero-card", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, accentBar: { classPropertyName: "accentBar", publicName: "accentBar", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.s-sunken": "surface() === 'sunken'", "class.s-primary": "surface() === 'primary'", "attr.data-surface": "surface() === 'primary' ? 'accent' : null", "class.a-subtle": "accent() === 'subtle'", "class.a-gradient": "accent() === 'gradient'", "class.has-bar": "accentBar()", "class.p-sm": "padding() === 'sm'", "class.p-md": "padding() === 'md'" } }, ngImport: i0, template: "<ng-content />\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;overflow:hidden;isolation:isolate;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-lg);padding:20px}:host(.p-sm){padding:10px}:host(.p-md){padding:16px}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle)}:host(.s-primary){background:var(--fold-color-primary);border-color:var(--fold-color-primary);color:var(--fold-color-on-primary)}:host(.a-subtle):after,:host(.a-gradient):after{content:\"\";position:absolute;inset:0;z-index:-1;pointer-events:none}:host(.a-subtle):after{background:radial-gradient(120% 140% at 100% 0%,color-mix(in srgb,var(--fold-color-primary) 10%,transparent),transparent 45%),linear-gradient(160deg,transparent 30%,color-mix(in srgb,var(--fold-color-surface-sunken) 50%,transparent))}:host(.a-gradient){border-color:var(--fold-color-primary-border)}:host(.a-gradient):after{background:radial-gradient(260px circle at 90% -10%,color-mix(in srgb,var(--fold-color-primary) 22%,transparent),transparent 68%),linear-gradient(180deg,color-mix(in srgb,var(--fold-color-primary) 8%,transparent),transparent 60%)}:host(.has-bar):before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(var(--fold-color-primary),color-mix(in srgb,var(--fold-color-primary) 30%,transparent))}\n"] });
2870
3019
  }
2871
3020
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldHeroCardComponent, decorators: [{
2872
3021
  type: Component,
2873
3022
  args: [{ selector: "fold-hero-card", standalone: true, host: {
2874
3023
  "[class.s-sunken]": "surface() === 'sunken'",
2875
3024
  "[class.s-primary]": "surface() === 'primary'",
3025
+ // a solid primary ground is an accent surface: auto-invert the content too.
3026
+ "[attr.data-surface]": "surface() === 'primary' ? 'accent' : null",
2876
3027
  "[class.a-subtle]": "accent() === 'subtle'",
2877
3028
  "[class.a-gradient]": "accent() === 'gradient'",
2878
3029
  "[class.has-bar]": "accentBar()",