{"version":3,"file":"styloviz-notification-center.mjs","names":[],"sources":["../../../../projects/notification-center-free/src/lib/notification-center.component.ts","../../../../projects/notification-center-free/src/lib/notification-center.component.html"],"sourcesContent":["import {\n  ChangeDetectionStrategy,\n  Component,\n  computed,\n  DestroyRef,\n  EmbeddedViewRef,\n  effect,\n  HostListener,\n  inject,\n  input,\n  model,\n  OnDestroy,\n  output,\n  PLATFORM_ID,\n  signal,\n  TemplateRef,\n  ViewContainerRef,\n  viewChild,\n} from '@angular/core';\nimport { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';\nimport {\n  createDebouncer,\n  svMergeRecords,\n  SvEmptyShellComponent,\n  SvErrorShellComponent,\n  SvSkeletonShellComponent,\n  SvOverlayStackService,\n  type SvOverlayHandle,\n  type SvDataError, SvStopPropagationDirective } from '@styloviz/core';\nimport { SvIconComponent } from '@styloviz/icons';\n\nimport type {\n  NotificationActionClickEvent,\n  NotificationCenterVariant,\n  NotificationClickEvent,\n  NotificationDismissEvent,\n  NotificationLoadMoreEvent,\n  NotificationMarkAllReadEvent,\n  NotificationItem,\n  NotificationPriority,\n  NotificationQuery,\n  NotificationType,\n} from './notification-center.types';\n\n// ─── Icon registry ────────────────────────────────────────────────────────────\n\n/**\n * Icon-library NAME per notification type.\n *\n * These were twelve hand-copied Heroicons paths piped through `[attr.d]` at\n * five call sites. A name resolves through the icon library, so `check:icons`\n * catches a typo and the free and Pro twins cannot drift apart in geometry.\n */\nconst TYPE_ICONS: Record<NotificationType, string> = {\n  comment:    'message-square',\n  mention:    'at-sign',\n  assignment: 'clipboard-list',\n  status:     'check-circle',\n  invite:     'user-plus',\n  approval:   'shield-check',\n  deadline:   'clock',\n  payment:    'credit-card',\n  security:   'shield-check',\n  update:     'refresh-cw',\n  reaction:   'smile',\n  system:     'info',\n};\n\n/**\n * ★★ STEP 700, BECAUSE EVERY ONE OF THESE FILLS IS DRAWN BEHIND `text-white`.\n *    All six call sites pair this map with white ink — two carry the actor's\n *    initials as text, four carry a glyph. At 500 the initials measured amber\n *    2.13:1, sky 2.71, primary 3.76 and violet 4.40 against white: an axe sweep\n *    of one page reported ten failing nodes and only saw those four hues,\n *    because which type renders depends on the fixture. 700 is the first step\n *    where every arm clears AA. Same fix, same reason, as `timeline`'s\n *    AVATAR_COLORS and `display-pro/comment`'s `avatarColor()`.\n *\n * ★  THE TWO NEAR-DUPLICATE HUES KEEP THEIR ONE-STEP OFFSET, so `payment` stays\n *    800 against `status`'s 700 and `security` stays 800 against `deadline`'s.\n *    green/emerald and red/rose are barely told apart at the same step, and this\n *    map only earns its keep while all 12 read as 12 — moving every arm to a\n *    flat 700 would have bought readability by spending the distinguishability\n *    that is the reason these stay literal at all.\n *\n *    `check:contrast` cannot see this: the ink is `text-white` in the template\n *    while the fill arrives from `typeColor()`, so no single element carries\n *    both halves of the pair.\n */\nconst TYPE_COLORS: Record<NotificationType, string> = {\n  comment:    'bg-primary-700',\n  mention:    'bg-violet-700',\n  assignment: 'bg-sky-700',\n  status:     'bg-emerald-700',\n  invite:     'bg-teal-700',\n  approval:   'bg-amber-700',\n  deadline:   'bg-rose-700',\n  payment:    'bg-green-800',\n  security:   'bg-red-800',\n  update:     'bg-indigo-700',\n  reaction:   'bg-pink-700',\n  system:     'bg-gray-700',\n};\n\nconst TYPE_ACCENT: Record<NotificationType, string> = {\n  comment:    'border-primary-400',\n  mention:    'border-violet-400',\n  assignment: 'border-sky-400',\n  status:     'border-emerald-400',\n  invite:     'border-teal-400',\n  approval:   'border-amber-400',\n  deadline:   'border-rose-400',\n  payment:    'border-green-500',\n  security:   'border-red-500',\n  update:     'border-indigo-400',\n  reaction:   'border-pink-400',\n  system:     'border-gray-400',\n};\n\nconst PRIORITY_COLORS: Record<NotificationPriority, string> = {\n  low:    'bg-gray-400',\n  medium: 'bg-warning-400',\n  high:   'bg-orange-500',\n  urgent: 'bg-danger-500',\n};\n\nfunction initials(name: string): string {\n  return name.split(' ').slice(0, 2).map(p => p[0]).join('').toUpperCase();\n}\n\nfunction timeAgo(iso: string): string {\n  const diff = (Date.now() - new Date(iso).getTime()) / 1000;\n  if (diff < 60)        return 'just now';\n  if (diff < 3600)      return `${Math.floor(diff / 60)}m ago`;\n  if (diff < 86400)     return `${Math.floor(diff / 3600)}h ago`;\n  if (diff < 86400 * 7) return `${Math.floor(diff / 86400)}d ago`;\n  return new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n}\n\n/** Midnight at the start of `d`, read with LOCAL getters — the user's own day. */\nfunction startOfDay(d: Date): number {\n  return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();\n}\n\n/**\n * The section heading a notification is filed under.\n *\n * ★ Counted in CALENDAR days in the reader's timezone, not in 24-hour windows\n * since now. The previous `Math.floor((Date.now() - then) / 86400000)` filed a\n * notification that arrived at 23:00 last night under **Today** when read at\n * 10:00 the next morning (11 elapsed hours floors to 0), and one from 20:00 two\n * days ago under **Yesterday** (38 hours floors to 1). Every heading was wrong\n * for whoever opened the panel early in the day, which is when a notification\n * inbox is actually read.\n *\n * Local getters, not `toISOString()`: the day boundary that matters is the\n * user's midnight, not UTC's.\n */\nfunction dateLabel(iso: string): string {\n  const then = new Date(iso);\n  // Local midnights differ by whole days ± an hour across a DST change, so\n  // round rather than floor.\n  const diff = Math.round((startOfDay(new Date()) - startOfDay(then)) / 86400000);\n  // A timestamp slightly in the future (clock skew between server and client)\n  // is still today's news, not \"This week\"'s.\n  if (diff <= 0) return 'Today';\n  if (diff === 1) return 'Yesterday';\n  if (diff < 7)  return 'This week';\n  if (diff < 30) return 'Earlier this month';\n  return 'Older';\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * SvNotificationCenterComponent — Premium notification inbox.\n *\n * Selector: `sv-notification-center`\n *\n * Six variants:\n *   - `dropdown`  Bell + floating panel with tabs, search & actions (default)\n *   - `drawer`    Bell + full-height right-side slide panel (Slack/Figma style)\n *   - `inline`    Embedded panel, no trigger — use on dedicated /notifications routes\n *   - `feed`      Activity-feed style list (GitHub/Jira style)\n *   - `compact`   Bell + slim dropdown, no tabs/search\n *   - `grouped`   Bell + dropdown with sticky date-group section headers\n *\n * Floating panels (dropdown, compact, grouped, drawer) are portaled to\n * document.body so they escape every overflow / transform / backdrop-filter\n * ancestor that would otherwise clip or misposition a fixed-positioned panel.\n *\n * @example\n * ```html\n * <sv-notification-center\n *   [notifications]=\"myNotifications\"\n *   variant=\"dropdown\"\n *   (notificationClick)=\"onNotifClick($event)\"\n *   (markAllRead)=\"markAllRead()\" />\n * ```\n */\n@Component({\n  selector: 'sv-notification-center',\n  standalone: true,\n  imports: [NgTemplateOutlet,\n    SvIconComponent,\n    SvSkeletonShellComponent,\n    SvErrorShellComponent,\n    SvEmptyShellComponent, SvStopPropagationDirective],\n  templateUrl: './notification-center.component.html',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  host: { class: 'block' },\n})\nexport class SvNotificationCenterComponent implements OnDestroy {\n  private readonly platformId = inject(PLATFORM_ID);\n  private readonly vcr        = inject(ViewContainerRef);\n\n  private readonly _floatingPanelTpl = viewChild<TemplateRef<unknown>>('floatingPanel');\n\n  private _portalContainer: HTMLElement | null         = null;\n  private readonly _overlayStack = inject(SvOverlayStackService);\n  /** Registration while the floating panel is mounted (`X-4`). */\n  private _stack: SvOverlayHandle | null = null;\n  private _portalView: EmbeddedViewRef<unknown> | null = null;\n\n  /** Trigger element kept for scroll-tracking repositioning. */\n  private _triggerEl: HTMLElement | null = null;\n  private _scrollHandler: (() => void) | null = null;\n  private _scrollRafId: number | null = null;\n\n  // ── Inputs ─────────────────────────────────────────────────────────────────\n\n  /** Presentation: dropdown, drawer, inline, feed, compact or grouped. @default 'dropdown' */\n  readonly variant       = input<NotificationCenterVariant>('dropdown');\n  /** The notifications to display. */\n  readonly notifications = input<NotificationItem[]>([]);\n  /** Max height of the scrollable list (CSS length). @default '400px' */\n  readonly maxHeight     = input<string>('400px');\n  /** Filter tab labels. @default ['All','Unread','Mentions','System'] */\n  readonly tabs          = input<string[]>(['All', 'Unread', 'Mentions', 'System']);\n  /** Panel heading text. @default 'Notifications' */\n  readonly panelTitle    = input<string>('Notifications');\n  /** Show the search box. @default true */\n  readonly showSearch    = input<boolean>(true);\n  /** Animate the bell icon on new notifications. @default true */\n  readonly animateBell   = input<boolean>(true);\n\n  /**\n   * Render the panel's \"View all\" control.\n   *\n   * ★ Off by default, and that default is the fix. The control used to be\n   * unconditional markup with no handler on it — a link to the notifications\n   * page that went nowhere, which is worse than no link because a user who\n   * presses it concludes the page is broken rather than absent. A host that has\n   * somewhere to send them opts in and handles `viewAll`; a host that does not\n   * never shows the control.\n   *\n   * @default false\n   */\n  readonly showViewAll = input<boolean>(false);\n\n  // ── Async state ─────────────────────────────────────────────────────────────\n\n  /**\n   * True while notifications are being fetched. Shows a placeholder in place of\n   * the list when there is nothing to show yet.\n   * @default false\n   */\n  readonly loading = input<boolean>(false);\n\n  /**\n   * Load failure. Use `toSvDataError()` from `@styloviz/core` to normalize an\n   * `HttpErrorResponse`.\n   * @default null\n   */\n  readonly error = input<SvDataError | string | null>(null);\n\n  /**\n   * The server's unread total for the WHOLE feed (`OF-10-I2`).\n   * `null` counts the rows in hand instead. @default null\n   *\n   * ★★★ THE BADGE WAS GUARANTEED WRONG FOR ANY PAGED FEED, not merely\n   *     \"unreconciled\" as the audit put it. `unreadCount` counted the rows\n   *     that had been DOWNLOADED, and this component ships `hasMore` — so a\n   *     user with 240 unread notifications and a first page of 20 saw a badge\n   *     reading 20, and it went UP as they scrolled. Pass the number the\n   *     server knows.\n   *\n   * ★★ IT IS RECONCILED, NOT TRUSTED BLINDLY. A total that the component\n   *    simply rendered would sit still while the user marked rows read in\n   *    front of it, which is the same class of bug from the other end. The\n   *    component subtracts the reads it is holding OPTIMISTICALLY — rows whose\n   *    local `read` is true while the `notifications` array you passed still\n   *    says false — so the badge drops on the click and the subtraction\n   *    disappears by itself when your updated rows arrive. Nothing is tracked\n   *    across changes, so a `rollback()` needs no bookkeeping either.\n   *\n   * ★ Send the updated rows and the new total together. Between the two the\n   *   delta is measured against rows you have not updated yet, which is a\n   *   transient off-by-one in the safe direction (too low, never too high).\n   */\n  readonly unreadTotal = input<number | null>(null);\n\n  /**\n   * What a redacted row shows instead of its title (`OF-10-I8`).\n   * @default 'This notification is no longer available'\n   */\n  readonly redactedLabel = input<string>('This notification is no longer available');\n\n  /**\n   * More notifications exist on the server. Renders the \"Load more\" control.\n   * A notification feed is unbounded — without this a host has to send the\n   * user's entire history to render a dropdown.\n   * @default false\n   */\n  readonly hasMore = input<boolean>(false);\n\n  /** True while the next page is being fetched. @default false */\n  readonly loadingMore = input<boolean>(false);\n\n  /**\n   * Ids with a write in flight. Those rows dim and stop responding, so a second\n   * click cannot race the first and leave `rollback()` ambiguous.\n   * @default []\n   */\n  readonly pendingIds = input<readonly string[] | ReadonlySet<string>>([]);\n\n  /**\n   * Ids whose last write FAILED, mapped to the message to show on that row.\n   *\n   * ★★★ `pendingIds` HAD NO COUNTERPART, AND THAT IS `OF-10-M2`. Optimistic\n   *     read/dismiss rolls back correctly (`OF-10-I1`, fixed in T0) — the row\n   *     reverts, and the user is told nothing. A notification that silently\n   *     un-dismissed itself looks like the click never landed, so they click\n   *     again, and again, against an endpoint that is refusing.\n   *\n   * ★★ PER ROW, not one banner. `error` above is for the FEED — \"could not\n   *    load notifications\" — and putting a single row's failure there says the\n   *    whole list is broken. The two are different facts and the audit's own\n   *    reasoning for `role=\"alert\"` vs `role=\"status\"` applies: scope the\n   *    message to what actually failed.\n   *\n   * ```ts\n   * this.itemErrors.set({ 'n-14': 'Could not mark as read.' });\n   * ```\n   *\n   * @default {}\n   */\n  readonly itemErrors = input<Readonly<Record<string, string>> | ReadonlyMap<string, string>>({});\n\n  /**\n   * Delay before `queryChange` fires, in milliseconds. `0` emits immediately.\n   * @default 300\n   */\n  readonly debounceMs = input<number>(300);\n\n  /**\n   * Filter and search on the server instead of in the component. Leaves the\n   * supplied `notifications` untouched — the host is responsible for returning\n   * only matching rows. Required for a real feed: filtering client-side can\n   * only ever search the page you already downloaded.\n   * @default false\n   */\n  readonly serverSide = input<boolean>(false);\n\n  // ── Controllable state ──────────────────────────────────────────────────────\n\n  /**\n   * Whether the panel is open. Two-way bindable, so a host can open the centre\n   * from a route or a keyboard shortcut. Ignored by the always-visible\n   * `inline` and `feed` variants.\n   */\n  readonly open = model<boolean>(false);\n\n  /**\n   * The selected tab label. Two-way bindable, so the tab survives a reload.\n   * @default 'All'\n   */\n  readonly activeTab = model<string>('All');\n\n  /** The search box contents. Two-way bindable. @default '' */\n  readonly search = model<string>('');\n\n  // ── Outputs ────────────────────────────────────────────────────────────────\n\n  /** Emitted when a notification row is clicked. */\n  readonly notificationClick = output<NotificationClickEvent>();\n  /** Emitted when an inline action on a notification is clicked. */\n  readonly actionClick       = output<NotificationActionClickEvent>();\n  /** Emitted when \"mark all as read\" is triggered. */\n  readonly markAllRead       = output<NotificationMarkAllReadEvent>();\n  /** Emitted when a notification is dismissed. */\n  readonly dismiss           = output<NotificationDismissEvent>();\n  /**\n   * The tab or search term changed — fetch the matching notifications.\n   * Debounced by `debounceMs`, and never emitted twice for the same query.\n   */\n  readonly queryChange       = output<NotificationQuery>();\n  /** The user asked for the next page. */\n  readonly loadMore          = output<NotificationLoadMoreEvent>();\n  /** The host should re-run the failed load. */\n  readonly retry             = output<void>();\n  /**\n   * The user asked for the full notifications surface — navigate there.\n   * Only reachable when `showViewAll` is set; the panel closes first, because\n   * leaving a floating panel open over the page it just navigated to is how a\n   * dropdown ends up covering its own destination.\n   */\n  readonly viewAll           = output<void>();\n\n  // ── State ──────────────────────────────────────────────────────────────────\n\n  // The panel's own state now lives in models, so a host can read, restore and\n  // deep-link it. These aliases keep the existing template bindings working.\n  readonly _open       = this.open;\n  readonly _activeTab  = this.activeTab;\n  readonly _search     = this.search;\n  readonly _items      = signal<NotificationItem[]>([]);\n\n  /**\n   * Optimistic writes the host has not confirmed yet, keyed by write\n   * (`OF-10-I1`). Each holds the ids it still applies to.\n   *\n   * ★★★ AN OVERLAY, NOT A SNAPSHOT, BECAUSE THE HOST'S LIST KEEPS MOVING.\n   *     The local list used to be a copy of `notifications` with the change\n   *     written into it, so any host push — a realtime arrival about a\n   *     different row — replaced the copy and threw the change away: a row the\n   *     user had just opened flicked back to unread until the server answered.\n   *     And `rollback()` restored a snapshot, which after a push either erased\n   *     the pushed rows or had to give up. Now the local list is always\n   *     `notifications` with the pending writes laid over it, so a push keeps\n   *     them and a rollback removes ONE write and nothing else.\n   *\n   * ★★ AN ENTRY ENDS IN ONE OF THREE WAYS: the host's list confirms it (the\n   *    row arrives read; a dismissed row stops arriving), the row disappears\n   *    from the host's list (nothing left to overlay), or its `rollback()` is\n   *    called. Confirmation is per id, so a mark-all-read the host confirms in\n   *    pages drains a page at a time.\n   */\n  private readonly _optimistic = new Map<number, { readonly kind: 'read' | 'dismiss'; readonly ids: Set<string> }>();\n  private _writeSeq = 0;\n\n  /** Debounced search term — what `queryChange` is built from. */\n  private readonly _debouncedSearch = createDebouncer('', {\n    delayMs: this.debounceMs,\n    destroyRef: inject(DestroyRef),\n  });\n\n  /** `pendingIds` normalized to a Set. */\n  private readonly _pending = computed<ReadonlySet<string>>(() => {\n    const ids = this.pendingIds();\n    return ids instanceof Set ? ids : new Set(ids as readonly string[]);\n  });\n\n  /** Whether this notification has a write in flight. */\n  isPending(item: NotificationItem): boolean {\n    return this._pending().has(item.id);\n  }\n\n  /** `itemErrors` normalized to a Map, so either shape can be bound. */\n  private readonly _itemErrors = computed<ReadonlyMap<string, string>>(() => {\n    const errors = this.itemErrors();\n    return errors instanceof Map\n      ? errors\n      : new Map(Object.entries(errors as Readonly<Record<string, string>>));\n  });\n\n  /** The message for this row's failed write, or `null`. */\n  errorFor(item: NotificationItem): string | null {\n    return this._itemErrors().get(item.id) ?? null;\n  }\n\n  /**\n   * Classes that make a row's async state VISIBLE.\n   *\n   * ★★★ `pendingIds` WAS BEHAVIOUR-ONLY, AND ITS OWN DOCSTRING SAID\n   *     OTHERWISE: \"those rows dim and stop responding\". The handlers did\n   *     refuse — `isPending` guarded every one of them — but `isPending` was\n   *     never referenced in the template, so nothing dimmed and nothing said\n   *     the click had landed. A row that refuses input and looks unchanged is\n   *     indistinguishable from a dead row, which is the same defect as\n   *     `OF-06-I7` one component over.\n   */\n  rowStateClass(item: NotificationItem): string {\n    return this.isPending(item) ? 'opacity-60 pointer-events-none' : '';\n  }\n\n  /** The row's \"Try again\" was pressed. */\n  readonly retryItem = output<NotificationItem>();\n\n  /**\n   * Re-run the failed write for this row.\n   *\n   * ★ No `stopPropagation` here: the block this button sits in carries\n   *   `svStopPropagation`, which the row's action buttons already use for the\n   *   same reason. Doing it in both places looked prudent and was not — a\n   *   mutation removing the directive stayed green, because the handler was\n   *   covering for it while leaving the MESSAGE TEXT beside the button still\n   *   falling through to the row's overlay button.\n   */\n  onRetryItem(item: NotificationItem): void {\n    // ★ A pending row has a write in flight; retrying it would be the double\n    //   submit `pendingIds` exists to prevent.\n    if (this.isPending(item)) return;\n    this.retryItem.emit(item);\n  }\n\n  /**\n   * Whether a search term or a non-default tab is narrowing the list — which\n   * decides whether an empty list means \"nothing here\" or \"nothing matching\".\n   */\n  readonly isFiltered = computed(() => {\n    const tabs = this.tabs();\n    return this.search().trim().length > 0 || this.activeTab() !== (tabs[0] ?? 'All');\n  });\n\n  /** Reset the search and return to the first tab. */\n  clearFilters(): void {\n    this.search.set('');\n    this.activeTab.set(this.tabs()[0] ?? 'All');\n  }\n  readonly _panelStyle = signal<{\n    top?: string;\n    bottom?: string;\n    right: string;\n    maxH?: string;\n  }>({ right: '0px' });\n\n  private _clickOutsideHandler: ((e: Event) => void) | null = null;\n\n  /** Last query handed to the host — guards against re-emitting the same one. */\n  private _lastQuery = '';\n\n  constructor() {\n    effect(() => {\n      const host = this.notifications();\n      this._prune(host);\n      this._items.set(this._overlay(host));\n    });   // no options: `allowSignalWrites` was removed in Angular 21 — it is\n         // ignored, and passing it logs a deprecation notice on every effect\n         // creation. One component rendered 18 times printed it 20 times.\n\n    // Feed the debouncer from the model, so a host writing `search` (restoring\n    // it from a URL, say) goes through the same path as typing.\n    effect(() => this._debouncedSearch.set(this.search()));\n\n    // One handler for the host whether the user typed or switched tab. The\n    // string guard matters: the query object is rebuilt on every recompute, so\n    // emitting on identity would fetch a result set already on screen.\n    effect(() => {\n      const query: NotificationQuery = {\n        tab: this.activeTab(),\n        search: this._debouncedSearch.value().trim(),\n      };\n      const key = `${query.tab}\\u0000${query.search}`;\n      if (key === this._lastQuery) return;\n      this._lastQuery = key;\n      this.queryChange.emit(query);\n    });\n\n\n    effect(() => {\n      // Clean up portal when variant changes while panel is open\n      if (!this._open()) {\n        this._destroyPortal();\n      }\n    });\n  }\n\n  /**\n   * Pending open-transition timer.\n   *\n   * ★ It has to be cleared on destroy. Without the handle the callback below\n   * still ran after teardown, mounting a `[data-nc-portal]` panel into\n   * `document.body` that nothing owned any more and attaching a document click\n   * listener and a window scroll listener that `ngOnDestroy` had already\n   * finished removing — so a route change made while the bell was being opened\n   * left an orphan panel on screen and two listeners running for the life of\n   * the tab. Matches the Pro twin, which already held the handle.\n   */\n  private _openTimer?: ReturnType<typeof setTimeout>;\n\n  ngOnDestroy(): void {\n    clearTimeout(this._openTimer);\n    this._removeClickOutside();\n    this._removeScrollListener();\n    this._destroyPortal();\n  }\n\n  // ── Computed ───────────────────────────────────────────────────────────────\n\n  /**\n   * The rows as the panel shows them: one per `id`, one per `dedupeKey`,\n   * ordered (`OF-10-I3`, `OF-10-M4`, `OF-10-M5`).\n   *\n   * ★★★ EVERY COUNT IS TAKEN OVER THIS, NOT OVER `_items` (`OF-10-I2`). The\n   *     raw list still holds the duplicates and the collapsed rows the panel\n   *     does not render, so the badge counted a repeated id twice and counted\n   *     every row folded into a `dedupeKey` group — and clicking the one row\n   *     the user could see marked only that row read, so the badge stayed up\n   *     over a list with nothing unread in it, permanently.\n   */\n  private readonly _merged = computed<readonly NotificationItem[]>(() =>\n    svMergeRecords<NotificationItem>([], this._items()).items,\n  );\n\n  /** Unread among the rows actually in hand — as rendered, see `_merged`. */\n  readonly loadedUnreadCount = computed(() => this._merged().filter(n => !n.read).length);\n\n  /**\n   * Rows this component has marked read that the host has not confirmed yet.\n   *\n   * ★ DERIVED, NOT COUNTED. An incremented counter would have to be unwound\n   *   by every `rollback()`, by a re-send of the same list, and by a dismissal\n   *   of an already-counted row — three places to forget. Comparing the local\n   *   list against the host's makes all three self-correcting.\n   */\n  private readonly _optimisticReads = computed<number>(() => {\n    const host = new Map(this.notifications().map(n => [n.id, n.read]));\n    // ★★ Over the RAW rows whenever a server total is in play: the server\n    //    counts every row, including the siblings a `dedupeKey` group folds\n    //    away. Opening a group of three marks three rows read, and subtracting\n    //    one made the badge jump by two when the host confirmed. Without a\n    //    total the badge counts rendered rows, so the rendered list is the\n    //    right basis there.\n    const rows = this.unreadTotal() === null ? this._merged() : this._items();\n    const seen = new Set<string>();\n    return rows.filter(n => {\n      if (!n.read || host.get(n.id) !== false || seen.has(n.id)) return false;\n      seen.add(n.id);\n      return true;\n    }).length;\n  });\n\n  /**\n   * What the badge shows: the server's total when there is one, minus the\n   * reads not yet confirmed (`OF-10-I2`). See {@link unreadTotal}.\n   */\n  readonly unreadCount = computed<number>(() => {\n    const total = this.unreadTotal();\n    if (total === null) return this.loadedUnreadCount();\n    return Math.max(0, total - this._optimisticReads());\n  });\n\n  readonly badgeLabel = computed(() => {\n    const c = this.unreadCount();\n    return c > 99 ? '99+' : c > 0 ? String(c) : '';\n  });\n\n  /**\n   * Classes for the trigger bell.\n   *\n   * `cssClass` on `<sv-icon>` is a single string, so the conditional\n   * `[class.animate-bounce]` the hand-drawn `<svg>` carried has to fold into\n   * the value. Kept identical to the Pro twin so the two cannot drift.\n   */\n  readonly bellCssClass = computed(() =>\n    this.animateBell() && this.unreadCount() > 0 ? 'w-5 h-5 animate-bounce' : 'w-5 h-5',\n  );\n\n  readonly filteredItems = computed(() => {\n    // With `serverSide`, the supplied list IS the answer to the current query.\n    // Filtering it again would hide rows the server deliberately returned.\n    const tab   = this.serverSide() ? 'All' : this._activeTab();\n    const query = this.serverSide() ? '' : this._search().toLowerCase().trim();\n    // ★★★ IDENTITY, VERSION, DEDUPLICATION AND ORDER IN ONE PASS, AND\n    //     DEFENSIVELY — the host owns this list and a realtime one arrives in\n    //     pieces. `@for` tracks by `item.id` and Angular THROWS on a duplicate\n    //     track key, so a naively appended stream did not render a muddled\n    //     panel: it rendered an exception. `svMergeRecords` collapses a\n    //     repeated id (keeping the higher `version`, not the later position),\n    //     collapses rows sharing a `dedupeKey`, and orders what is left.\n    //\n    // ★★ THE ORDER RULE MOVED WITH IT, and it fixes two things the inline sort\n    //    got wrong. `new Date('n/a').getTime()` is NaN, and a comparator that\n    //    returns NaN makes the sort's result implementation-defined — one\n    //    unparsed timestamp from one integration and the same data renders in\n    //    a different order from pass to pass. And rows that tie now break the\n    //    tie on `id`, so a live list does not twitch when something unrelated\n    //    changes.\n    //\n    // ★ `svMergeRecords([], …)` rather than `svOrderRecords`: merging nothing\n    //   into the list is how the collapse rules get applied to it, and the\n    //   report is discarded here because there is nobody to report to — the\n    //   host merges its own stream and reads it there.\n    let list: NotificationItem[] = [\n      ...this._merged(),\n    // ★★★ REDACTION HAPPENS HERE, BEFORE THE SEARCH FILTER BELOW (`OF-10-I8`).\n    //     Sanitising what is RENDERED would still leave the hidden title\n    //     searchable: type it, watch a row appear, and the search box is an\n    //     oracle for the thing being withheld. Doing it in front of the filter\n    //     means the real title is not in the list the query runs over — and\n    //     not in the template at all, so it cannot leak through a title\n    //     attribute, an aria-label or a render site added later.\n    ].map(n => this._redact(n));\n    if (tab === 'Unread')        list = list.filter(n => !n.read);\n    else if (tab === 'Mentions') list = list.filter(n => n.type === 'mention');\n    else if (tab === 'System')   list = list.filter(n => ['system','update','security'].includes(n.type));\n    if (query) {\n      list = list.filter(n =>\n        n.title.toLowerCase().includes(query) ||\n        n.body?.toLowerCase().includes(query) ||\n        n.actor?.name.toLowerCase().includes(query) ||\n        n.target?.label.toLowerCase().includes(query)\n      );\n    }\n    return list;\n  });\n\n  readonly groupedItems = computed(() => {\n    const map = new Map<string, NotificationItem[]>();\n    for (const n of this.filteredItems()) {\n      const label = dateLabel(n.timestamp);\n      if (!map.has(label)) map.set(label, []);\n      map.get(label)!.push(n);\n    }\n    const ORDER = ['Today', 'Yesterday', 'This week', 'Earlier this month', 'Older'];\n    return [...map.entries()]\n      .sort((a, b) => ORDER.indexOf(a[0]) - ORDER.indexOf(b[0]))\n      .map(([label, items]) => ({ label, items }));\n  });\n\n  readonly tabUnread = computed<Record<string, number>>(() => {\n    const all     = this._merged();\n    // ★ The All / Unread tabs count the same thing the bell does. With a\n    //   server `unreadTotal` they used to show the size of the page in hand\n    //   next to a badge showing the total — two numbers for one fact.\n    const unread  = this.unreadCount();\n    const mention = all.filter(n => n.type === 'mention' && !n.read).length;\n    const sys     = all.filter(n => ['system','update','security'].includes(n.type) && !n.read).length;\n    return { All: unread, Unread: unread, Mentions: mention, System: sys };\n  });\n\n  // ── Panel open / close ─────────────────────────────────────────────────────\n\n  /**\n   * Toggles the floating panel. On open, portals the panel to document.body\n   * so it is never clipped by overflow:hidden, transform, or backdrop-filter\n   * on any ancestor element.\n   */\n  togglePanel(event: MouseEvent): void {\n    if (this._open()) {\n      this._open.set(false);\n      this._destroyPortal();\n      this._removeClickOutside();\n      this._removeScrollListener();\n      return;\n    }\n\n    if (!isPlatformBrowser(this.platformId)) return;\n\n    this._triggerEl = event.currentTarget as HTMLElement;\n    const rect = this._triggerEl.getBoundingClientRect();\n    this._panelStyle.set(this._computePosition(rect));\n    this._open.set(true);\n\n    // setTimeout allows Angular to process the signal update so the viewChild\n    // template ref is available before _createPortal() runs.\n    this._openTimer = setTimeout(() => {\n      this._createPortal();\n      this._attachClickOutside();\n      this._attachScrollListener();\n    }, 0);\n  }\n\n  closePanel(): void {\n    this._open.set(false);\n    this._destroyPortal();\n    this._removeClickOutside();\n    this._removeScrollListener();\n  }\n\n  /** Close the floating panel when Escape is pressed while it is open. */\n  @HostListener('document:keydown.escape')\n  onEscape(): void {\n    if (!this._open()) return;\n    // ★ `X-4`: only the topmost surface owns Escape. A confirm dialog opened\n    //   from a notification's action used to close the panel under it too —\n    //   one keystroke, two surfaces gone.\n    if (this._stack && !this._stack.isTopmost()) return;\n    this.closePanel();\n  }\n\n  // ── Portal helpers ─────────────────────────────────────────────────────────\n\n  /**\n   * Computes a viewport-safe position for the floating panel.\n   *\n   * Horizontal: right-aligned with the trigger's right edge, clamped so the\n   *   panel never overflows the left viewport edge.\n   *\n   * Vertical (flip logic):\n   *   - Prefer BELOW the trigger when there is at least 280 px of clearance.\n   *   - Flip ABOVE when there is more space above than below.\n   *   - `maxH` caps the panel so it never clips the opposite viewport edge.\n   */\n  private _computePosition(rect: DOMRect): {\n    top?: string;\n    bottom?: string;\n    right: string;\n    maxH?: string;\n  } {\n    const GAP    = 8;\n    const EDGE   = 8;\n    const vw     = window.innerWidth;\n    const vh     = window.innerHeight;\n    const panelW = 416;\n\n    // ── Horizontal ──────────────────────────────────────────────────────────\n    const rawRight = vw - rect.right;\n    const rightVal = Math.max(EDGE, Math.min(rawRight, vw - panelW - EDGE));\n\n    // ── Vertical (flip) ─────────────────────────────────────────────────────\n    const spaceBelow = vh - rect.bottom - GAP - EDGE;\n    const spaceAbove = rect.top      - GAP - EDGE;\n    const minFit     = 280; // px — minimum useful panel height\n\n    if (spaceBelow >= minFit || spaceBelow >= spaceAbove) {\n      // Show below — anchor top, cap height\n      return {\n        top:   `${rect.bottom + GAP}px`,\n        right: `${rightVal}px`,\n        maxH:  `${Math.max(minFit, spaceBelow)}px`,\n      };\n    } else {\n      // Flip above — anchor bottom (panel grows upward), cap height\n      return {\n        bottom: `${vh - rect.top + GAP}px`,\n        right:  `${rightVal}px`,\n        maxH:   `${Math.max(minFit, spaceAbove)}px`,\n      };\n    }\n  }\n\n  /**\n   * Appends a portal host <div> to document.body and renders the\n   * #floatingPanel template into it via Angular's ViewContainerRef.\n   *\n   * The host div has no transform / filter / overflow, so position:fixed\n   * children are viewport-relative in all browsers.\n   */\n  private _createPortal(): void {\n    const tpl = this._floatingPanelTpl();\n    if (!tpl || this._portalContainer) return;\n\n    const container = document.createElement('div');\n    container.setAttribute('data-nc-portal', '');\n    // ★★ `X-4`: THE Z-INDEX COMES FROM THE OVERLAY STACK. The panel's own\n    //    `z-[9999]` used to be its page-level z-index — a number of its own,\n    //    below nothing and above everything, so a confirm dialog opened from a\n    //    notification's action painted UNDER the panel it came from, and a\n    //    dropdown opened inside the panel under it too. The container is now\n    //    a positioned stacking context at the stack's value; the classes on\n    //    the panel only order its own backdrop and sheet inside it.\n    this._stack = this._overlayStack.push('anchored', { element: () => this._portalContainer });\n    container.style.position = 'relative';\n    container.style.zIndex = String(this._stack.zIndex);\n    document.body.appendChild(container);\n    this._portalContainer = container;\n\n    // EmbeddedViewRef remains in Angular's view/CD tree even though\n    // its DOM nodes are moved to document.body.\n    const view = this.vcr.createEmbeddedView(tpl);\n    view.rootNodes.forEach(node => container.appendChild(node));\n    this._portalView = view;\n  }\n\n  private _destroyPortal(): void {\n    // Every close path — toggle, closePanel, the `open` model, destroy — ends\n    // here, so this is the one release point.\n    this._stack?.release();\n    this._stack = null;\n    this._portalView?.destroy();\n    this._portalView = null;\n    this._portalContainer?.remove();\n    this._portalContainer = null;\n  }\n\n  // ── Scroll / resize tracking ───────────────────────────────────────────────\n\n  /**\n   * Recomputes panel position on every scroll / resize so the panel\n   * tracks the trigger button as the page scrolls.\n   * Uses requestAnimationFrame to throttle updates.\n   */\n  private _attachScrollListener(): void {\n    this._removeScrollListener();\n    let pending = false;\n    this._scrollHandler = () => {\n      if (pending) return;\n      pending = true;\n      this._scrollRafId = requestAnimationFrame(() => {\n        pending = false;\n        if (this._triggerEl && this._open()) {\n          this._panelStyle.set(this._computePosition(this._triggerEl.getBoundingClientRect()));\n        }\n      });\n    };\n    // capture:true catches scroll events on any nested scrollable ancestor\n    window.addEventListener('scroll', this._scrollHandler, { passive: true, capture: true });\n    window.addEventListener('resize', this._scrollHandler, { passive: true });\n  }\n\n  private _removeScrollListener(): void {\n    // ★ SSR: `ngOnDestroy` runs at the end of a prerender. The handler is only\n    // ever assigned in the browser, so this is belt-and-braces — but the belt\n    // is what keeps one component from failing a whole route.\n    if (this._scrollHandler && typeof window !== 'undefined') {\n      window.removeEventListener('scroll', this._scrollHandler, true);\n      window.removeEventListener('resize', this._scrollHandler);\n      this._scrollHandler = null;\n    }\n    if (this._scrollRafId !== null) {\n      cancelAnimationFrame(this._scrollRafId);\n      this._scrollRafId = null;\n    }\n  }\n\n  // ── Click-outside ──────────────────────────────────────────────────────────\n\n  private _attachClickOutside(): void {\n    this._removeClickOutside();\n    this._clickOutsideHandler = (e: Event) => {\n      const target   = e.target as Element;\n      const inHost   = target.closest('sv-notification-center, sv-notification-center');\n      const inPortal = target.closest('[data-nc-portal]');\n      // ★ `X-4`: a click inside a surface opened FROM the panel (a menu, a\n      //   dialog — portalled elsewhere) is not \"outside\" it.\n      if (this._stack?.isInsideHigherSurface(target)) return;\n      if (!inHost && !inPortal) this.closePanel();\n    };\n    document.addEventListener('click', this._clickOutsideHandler, true);\n  }\n\n  private _removeClickOutside(): void {\n    // ★ SSR: see `_removeScrollListener` above.\n    if (this._clickOutsideHandler && typeof document !== 'undefined') {\n      document.removeEventListener('click', this._clickOutsideHandler, true);\n      this._clickOutsideHandler = null;\n    }\n  }\n\n  // ── Actions ────────────────────────────────────────────────────────────────\n\n  /** The host's rows with every pending write laid over them. */\n  private _overlay(host: readonly NotificationItem[]): NotificationItem[] {\n    const read = new Set<string>();\n    const gone = new Set<string>();\n    for (const op of this._optimistic.values()) {\n      for (const id of op.ids) (op.kind === 'read' ? read : gone).add(id);\n    }\n    return host\n      .filter(n => !gone.has(n.id))\n      .map(n => (read.has(n.id) && !n.read ? { ...n, read: true } : { ...n }));\n  }\n\n  /** Drop what the host's list has confirmed, or no longer contains. */\n  private _prune(host: readonly NotificationItem[]): void {\n    const byId = new Map<string, boolean>();\n    // A repeated id counts as read only if every copy is.\n    for (const n of host) byId.set(n.id, (byId.get(n.id) ?? true) && n.read);\n    for (const [seq, op] of this._optimistic) {\n      for (const id of [...op.ids]) {\n        const read = byId.get(id);\n        const settled = read === undefined || (op.kind === 'read' && read);\n        if (settled) op.ids.delete(id);\n      }\n      if (op.ids.size === 0) this._optimistic.delete(seq);\n    }\n  }\n\n  /**\n   * Record an optimistic write and return its `rollback` handle.\n   *\n   * ★ The handle undoes THIS write only, whenever it is called. It used to\n   *   restore a whole-list snapshot, which is why it had to refuse once\n   *   anything else had changed — and a refused rollback left the failed write\n   *   on screen for good. Removing one entry from the overlay cannot undo\n   *   anything else, so there is nothing to refuse. Once the host has\n   *   confirmed (or dropped) the rows, the entry is gone and the call is a\n   *   no-op.\n   */\n  private _write(kind: 'read' | 'dismiss', ids: Iterable<string>): () => void {\n    const seq = ++this._writeSeq;\n    const set = new Set(ids);\n    if (set.size > 0) this._optimistic.set(seq, { kind, ids: set });\n    this._items.set(this._overlay(this.notifications()));\n    return () => {\n      if (!this._optimistic.delete(seq)) return;\n      this._items.set(this._overlay(this.notifications()));\n    };\n  }\n\n  /**\n   * The rows one press on `item` acts on: itself, and every row collapsed into\n   * it by a shared `dedupeKey` (`OF-10-M4`).\n   *\n   * ★★ THE ROW IS ONE EVENT, SO THE WRITE IS TOO. Acting on the survivor alone\n   *    left its siblings behind: opening it kept them unread (a badge nothing\n   *    visible explains), and dismissing it promoted the next sibling into its\n   *    place — the notification the user just dismissed came straight back.\n   */\n  private _groupOf(item: NotificationItem): ReadonlySet<string> {\n    const key = item.dedupeKey;\n    const ids = new Set<string>([item.id]);\n    if (key === undefined) return ids;\n    for (const n of this._items()) if (n.dedupeKey === key) ids.add(n.id);\n    return ids;\n  }\n\n  onNotificationClick(item: NotificationItem): void {\n    if (this.isPending(item)) return;\n    const group = this._groupOf(item);\n    const collapsedIds = this._items()\n      .filter(n => n.id !== item.id && group.has(n.id) && !n.read)\n      .map(n => n.id);\n    const notification = { ...this._original(item), read: true };\n    const rollback = this._write('read', group);\n    this.notificationClick.emit({\n      notification,\n      rollback,\n      ...(collapsedIds.length ? { collapsedIds } : {}),\n    });\n  }\n\n  /**\n   * A redacted row with its content removed rather than merely hidden.\n   *\n   * ★ `body`, `actor`, `target` and `actions` go with the title: an actor's\n   *   name and a target's label are sentences about the same record, and an\n   *   action button would post to a resource the user may not touch.\n   */\n  private _redact(item: NotificationItem): NotificationItem {\n    if (!item.redacted) return item;\n    return {\n      ...item,\n      title: this.redactedLabel(),\n      body: undefined,\n      actor: undefined,\n      target: undefined,\n      actions: undefined,\n    };\n  }\n\n  /**\n   * The host's own row for this id.\n   *\n   * ★ The template holds the REDACTED copy, so an event emitted straight from\n   *   it would hand the host back a row whose title this component invented.\n   *   The host owns the real one — including `redacted: true`, which is what\n   *   lets it decide what a click on such a row means.\n   */\n  private _original(item: NotificationItem): NotificationItem {\n    return this._items().find(n => n.id === item.id) ?? item;\n  }\n\n  onActionClick(item: NotificationItem, actionId: string, event: Event): void {\n    event.stopPropagation();\n    // ★ A redacted row renders no actions, so this is the public-API arm of\n    //   the same rule: the action would post to a record the user cannot see.\n    if (item.redacted) return;\n    this.actionClick.emit({ notification: this._original(item), actionId });\n  }\n\n  onDismiss(item: NotificationItem, event: Event): void {\n    event.stopPropagation();\n    if (this.isPending(item)) return;\n    // ★ Resolved BEFORE the removal: afterwards there is no row left to\n    //   resolve, and the host would be handed the redacted copy.\n    const real = this._original(item);\n    const group = this._groupOf(item);\n    const collapsedIds = [...group].filter(id => id !== item.id);\n    // The rollback rebuilds from the host's list, so the row returns in its\n    // original position rather than at the end.\n    const rollback = this._write('dismiss', group);\n    this.dismiss.emit({\n      notification: real,\n      rollback,\n      ...(collapsedIds.length ? { collapsedIds } : {}),\n    });\n  }\n\n  onMarkAllRead(): void {\n    const unreadIds = this._items().filter(n => !n.read).map(n => n.id);\n    const rollback = this._write('read', unreadIds);\n    this.markAllRead.emit({ unreadIds, rollback });\n  }\n\n  /**\n   * Hand the user to the full notifications surface.\n   *\n   * The panel closes on the way out: it is portalled to `document.body` at a\n   * fixed position, so one left open would hang over the page it just navigated\n   * to with nothing on that page able to dismiss it.\n   */\n  onViewAll(): void {\n    this.closePanel();\n    this.viewAll.emit();\n  }\n\n  /** Ask the host for the next page of notifications. */\n  onLoadMore(): void {\n    this.loadMore.emit({\n      loaded: this._items().length,\n      query: { tab: this.activeTab(), search: this.search().trim() },\n    });\n  }\n\n  setTab(tab: string): void {\n    this.activeTab.set(tab);\n    this.search.set('');\n  }\n\n  // ── Template helpers ───────────────────────────────────────────────────────\n\n  typeIcon(type: NotificationType): string    { return TYPE_ICONS[type];    }\n  typeColor(type: NotificationType): string   { return TYPE_COLORS[type];   }\n  typeAccent(type: NotificationType): string  { return TYPE_ACCENT[type];   }\n  priorityColor(p: NotificationPriority): string { return PRIORITY_COLORS[p]; }\n  initials(name: string): string  { return initials(name);  }\n  timeAgo(iso: string): string    { return timeAgo(iso);    }\n  dateLabel(iso: string): string  { return dateLabel(iso);  }\n}\n","<!-- ─── Notification Center ────────────────────────────────────────────────── -->\n<!-- Bell-trigger variants render only the trigger here.                        -->\n<!-- Floating panels are rendered via a body-level portal (#floatingPanel)      -->\n<!-- so they escape every overflow / transform / backdrop-filter ancestor.      -->\n\n@switch (variant()) {\n\n  <!-- ═══════════════════════════════════════════════════════════════════════ -->\n  <!-- VARIANT: dropdown  — bell trigger only; panel portaled to body         -->\n  <!-- ═══════════════════════════════════════════════════════════════════════ -->\n  @case ('dropdown') {\n    <div class=\"relative inline-flex\">\n      <button\n        type=\"button\"\n        (click)=\"togglePanel($event)\"\n        [attr.aria-expanded]=\"_open()\"\n        aria-haspopup=\"true\"\n        aria-label=\"Open notifications\"\n        class=\"relative inline-flex items-center justify-center w-10 h-10 rounded-xl\n               text-gray-500 dark:text-gray-400\n               hover:text-gray-700 dark:hover:text-gray-200\n               hover:bg-gray-100 dark:hover:bg-gray-800\n               focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500\n               transition-colors duration-150\">\n        <sv-icon name=\"bell\" [cssClass]=\"bellCssClass()\" [strokeWidth]=\"1.8\" />\n        @if (badgeLabel()) {\n          <span class=\"absolute -top-0.5 -right-0.5 min-w-[18px] h-[18px] px-1\n                       flex items-center justify-center rounded-full\n                       bg-danger-600 text-white text-[10px] font-bold leading-none\n                       ring-2 ring-white dark:ring-gray-900\">\n            {{ badgeLabel() }}\n          </span>\n        }\n      </button>\n    </div>\n  }\n\n  <!-- ═══════════════════════════════════════════════════════════════════════ -->\n  <!-- VARIANT: drawer  — bell trigger only; panel portaled to body           -->\n  <!-- ═══════════════════════════════════════════════════════════════════════ -->\n  @case ('inline') {\n    <div class=\"rounded-2xl border border-gray-200 dark:border-gray-700\n                bg-white dark:bg-gray-900 shadow-sm overflow-hidden\">\n\n      <div class=\"flex items-center justify-between px-5 py-4\n                  border-b border-gray-100 dark:border-gray-800\">\n        <div class=\"flex items-center gap-2.5\">\n          <h2 class=\"text-base font-semibold text-gray-900 dark:text-gray-100\">{{ panelTitle() }}</h2>\n          @if (unreadCount() > 0) {\n            <span class=\"px-2 py-0.5 rounded-full bg-primary-100 dark:bg-primary-900/40\n                         text-primary-700 dark:text-primary-300 text-xs font-semibold\">\n              {{ unreadCount() }}\n            </span>\n          }\n        </div>\n        @if (unreadCount() > 0) {\n          <button type=\"button\" (click)=\"onMarkAllRead()\"\n                  class=\"text-xs text-primary-600 dark:text-primary-400 hover:underline focus-visible:outline-none\">\n            Mark all read\n          </button>\n        }\n      </div>\n\n      @if (showSearch()) {\n        <div class=\"px-5 pt-3.5\">\n          <div class=\"relative\">\n            <sv-icon name=\"search\" cssClass=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500 dark:text-gray-400 pointer-events-none\" [strokeWidth]=\"2\" />\n            <input type=\"search\" placeholder=\"Search notifications…\"\n                   [value]=\"_search()\"\n                   (input)=\"_search.set($any($event.target).value)\"\n                   class=\"w-full pl-9 pr-3 py-2.5 text-sm rounded-xl\n                          bg-gray-50 dark:bg-gray-800\n                          border border-gray-200 dark:border-gray-700\n                          text-gray-900 dark:text-gray-100\n                          placeholder:text-gray-500 dark:placeholder:text-gray-400\n                          focus:outline-none focus:ring-2 focus:ring-primary-500/40\"/>\n          </div>\n        </div>\n      }\n\n      @if (tabs().length > 0) {\n        <div class=\"flex gap-0.5 px-5 pt-3 border-b border-gray-100 dark:border-gray-800\">\n          @for (tab of tabs(); track tab) {\n            <button type=\"button\" (click)=\"setTab(tab)\"\n                    class=\"relative px-3 py-2 text-sm font-medium rounded-t-lg focus-visible:outline-none transition-colors\"\n                    [class]=\"_activeTab() === tab\n                      ? 'text-primary-600 dark:text-primary-400'\n                      : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200'\">\n              {{ tab }}\n              @if (tabUnread()[tab] > 0) {\n                <span class=\"ml-1 px-1.5 py-0.5 rounded-full bg-danger-100 dark:bg-danger-900/40 text-danger-700 dark:text-danger-400 text-xs font-bold\">\n                  {{ tabUnread()[tab] }}\n                </span>\n              }\n              @if (_activeTab() === tab) {\n                <span class=\"absolute bottom-0 left-0 right-0 h-0.5 bg-primary-500 rounded-full\"></span>\n              }\n            </button>\n          }\n        </div>\n      }\n\n      <div class=\"overflow-y-auto\" [style.max-height]=\"maxHeight()\">\n        @if (filteredItems().length === 0) {\n          <ng-container *ngTemplateOutlet=\"emptyTpl\"></ng-container>\n        } @else {\n          @for (item of filteredItems(); track item.id) {\n            <ng-container *ngTemplateOutlet=\"notifRow; context: { $implicit: item }\"></ng-container>\n          }\n<!-- Unbounded feed: the next page, on demand. -->\n@if (hasMore() || loadingMore()) {\n  <div class=\"px-3 py-2\">\n    @if (loadingMore()) {\n      <div data-sv-state=\"loading-more\">\n        <sv-skeleton-shell variant=\"list\" [rows]=\"2\" ariaLabel=\"Loading more notifications\" />\n      </div>\n    } @else {\n      <button\n        type=\"button\"\n        class=\"w-full rounded-lg border border-dashed border-gray-200 dark:border-gray-700 px-3 py-2 text-xs font-medium text-gray-500 dark:text-gray-400 transition-colors hover:border-primary-300 hover:text-primary-600 dark:hover:border-primary-700 dark:hover:text-primary-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500\"\n        (click)=\"onLoadMore()\">\n        Load more\n      </button>\n    }\n  </div>\n}\n\n        }\n      </div>\n    </div>\n  }\n\n  <!-- ═══════════════════════════════════════════════════════════════════════ -->\n  <!-- VARIANT: feed  — activity-feed style list                              -->\n  <!-- ═══════════════════════════════════════════════════════════════════════ -->\n}\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n     PORTAL TEMPLATE — rendered to document.body on open\n     No position:fixed ancestor clipping can reach here.\n     ═══════════════════════════════════════════════════════════════════════════ -->\n<ng-template #floatingPanel>\n  @switch (variant()) {\n\n    <!-- ─── Dropdown panel ──────────────────────────────────────────────── -->\n    @case ('dropdown') {\n      <div\n        class=\"fixed z-[9999] w-[26rem] max-w-[calc(100vw-1rem)]\n               rounded-2xl shadow-2xl shadow-black/15 dark:shadow-black/50\n               bg-white dark:bg-gray-900\n               border border-gray-200/70 dark:border-gray-700/60\n               overflow-hidden flex flex-col\"\n        [style.top]=\"_panelStyle().top\"\n        [style.bottom]=\"_panelStyle().bottom\"\n        [style.right]=\"_panelStyle().right\"\n        [style.max-height]=\"_panelStyle().maxH\">\n\n        <!-- Header -->\n        <div class=\"flex items-center justify-between px-5 py-4\n                    border-b border-gray-100 dark:border-gray-800\">\n          <div class=\"flex items-center gap-2.5\">\n            <span class=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">{{ panelTitle() }}</span>\n            @if (unreadCount() > 0) {\n              <span class=\"px-2 py-0.5 rounded-full bg-primary-100 dark:bg-primary-900/40\n                           text-primary-700 dark:text-primary-300 text-[11px] font-semibold\">\n                {{ unreadCount() }} new\n              </span>\n            }\n          </div>\n          <div class=\"flex items-center gap-2\">\n            @if (unreadCount() > 0) {\n              <button type=\"button\" (click)=\"onMarkAllRead()\"\n                      class=\"text-xs text-primary-600 dark:text-primary-400 hover:underline focus-visible:outline-none\">\n                Mark all read\n              </button>\n            }\n            <button type=\"button\" (click)=\"closePanel()\" aria-label=\"Close\"\n                    class=\"w-7 h-7 flex items-center justify-center rounded-lg\n                           text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300\n                           hover:bg-gray-100 dark:hover:bg-gray-800\n                           focus-visible:outline-none transition-colors\">\n              <sv-icon name=\"close\" cssClass=\"w-4 h-4\" [strokeWidth]=\"2\" />\n            </button>\n          </div>\n        </div>\n\n        <!-- Search -->\n        @if (showSearch()) {\n          <div class=\"px-4 pt-3\">\n            <div class=\"relative\">\n              <sv-icon name=\"search\" cssClass=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500 dark:text-gray-400 pointer-events-none\" [strokeWidth]=\"2\" />\n              <input type=\"search\" placeholder=\"Search notifications…\"\n                     [value]=\"_search()\"\n                     (input)=\"_search.set($any($event.target).value)\"\n                     class=\"w-full pl-9 pr-3 py-2 text-sm rounded-xl\n                            bg-gray-50 dark:bg-gray-800\n                            border border-gray-200 dark:border-gray-700\n                            text-gray-900 dark:text-gray-100\n                            placeholder:text-gray-500 dark:placeholder:text-gray-400\n                            focus:outline-none focus:ring-2 focus:ring-primary-500/40 focus:border-primary-400\n                            transition-shadow\"/>\n            </div>\n          </div>\n        }\n\n        <!-- Tabs -->\n        @if (tabs().length > 0) {\n          <div class=\"flex gap-0.5 px-4 pt-3 border-b border-gray-100 dark:border-gray-800\">\n            @for (tab of tabs(); track tab) {\n              <button type=\"button\" (click)=\"setTab(tab)\"\n                      class=\"relative px-3 py-2 text-xs font-medium rounded-t-lg\n                             focus-visible:outline-none transition-colors\"\n                      [class]=\"_activeTab() === tab\n                        ? 'text-primary-600 dark:text-primary-400'\n                        : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200'\">\n                {{ tab }}\n                @if (tabUnread()[tab] > 0) {\n                  <span class=\"ml-1 px-1.5 py-0.5 rounded-full\n                               bg-danger-100 dark:bg-danger-900/40\n                               text-danger-700 dark:text-danger-400 text-[10px] font-bold\">\n                    {{ tabUnread()[tab] }}\n                  </span>\n                }\n                @if (_activeTab() === tab) {\n                  <span class=\"absolute bottom-0 left-0 right-0 h-0.5 bg-primary-500 rounded-full\"></span>\n                }\n              </button>\n            }\n          </div>\n        }\n\n        <!-- List -->\n        <div class=\"overflow-y-auto\" [style.max-height]=\"maxHeight()\">\n          @if (filteredItems().length === 0) {\n            <ng-container *ngTemplateOutlet=\"emptyTpl\"></ng-container>\n          } @else {\n            @for (item of filteredItems(); track item.id) {\n              <ng-container *ngTemplateOutlet=\"notifRow; context: { $implicit: item }\"></ng-container>\n            }\n<!-- Unbounded feed: the next page, on demand. -->\n@if (hasMore() || loadingMore()) {\n  <div class=\"px-3 py-2\">\n    @if (loadingMore()) {\n      <div data-sv-state=\"loading-more\">\n        <sv-skeleton-shell variant=\"list\" [rows]=\"2\" ariaLabel=\"Loading more notifications\" />\n      </div>\n    } @else {\n      <button\n        type=\"button\"\n        class=\"w-full rounded-lg border border-dashed border-gray-200 dark:border-gray-700 px-3 py-2 text-xs font-medium text-gray-500 dark:text-gray-400 transition-colors hover:border-primary-300 hover:text-primary-600 dark:hover:border-primary-700 dark:hover:text-primary-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500\"\n        (click)=\"onLoadMore()\">\n        Load more\n      </button>\n    }\n  </div>\n}\n\n          }\n        </div>\n\n        <!-- Footer -->\n        <div class=\"px-5 py-3 border-t border-gray-100 dark:border-gray-800\n                    flex items-center justify-between\">\n          <span class=\"text-xs text-gray-500 dark:text-gray-400\">\n            {{ filteredItems().length }} notification{{ filteredItems().length !== 1 ? 's' : '' }}\n          </span>\n          @if (showViewAll()) {\n            <button type=\"button\" (click)=\"onViewAll()\"\n                    class=\"text-xs font-medium text-primary-600 dark:text-primary-400\n                           hover:underline focus-visible:outline-none\">\n              View all\n            </button>\n          }\n        </div>\n      </div>\n    }\n\n    <!-- ─── Compact panel ───────────────────────────────────────────────── -->\n  }\n</ng-template>\n\n<!-- ─── Shared: notification row ─────────────────────────────────────────────\n     Used by dropdown / drawer / inline / grouped (via *ngTemplateOutlet).\n     Context: { $implicit: NotificationItem }\n     ──────────────────────────────────────────────────────────────────────── -->\n<ng-template #notifRow let-item>\n  <div\n    class=\"group relative flex gap-3.5 px-5 py-4 cursor-pointer\n           border-b border-gray-100 dark:border-gray-800/60 last:border-0\n           transition-colors duration-100\"\n    [attr.aria-busy]=\"isPending(item) ? 'true' : null\"\n    [class]=\"(!item.read\n      ? 'bg-primary-50/50 dark:bg-primary-950/20 hover:bg-primary-50/80 dark:hover:bg-primary-950/30'\n      : 'hover:bg-gray-50/80 dark:hover:bg-gray-800/50') + ' ' + rowStateClass(item)\">\n\n    <!--\n      ★★ THE ROW WAS A <button> WRAPPING BUTTONS, which is not merely an ARIA\n      problem: `<button>` has no permitted button descendants, so the parser\n      reparents the action and dismiss controls out of the row entirely, and\n      `button` being children-presentational in ARIA hid whatever survived.\n\n      A row-sized overlay button is the activation target instead. A pointer\n      click anywhere on the row still opens the notification; the controls that\n      must stay separately operable are lifted onto `z-10` above it.\n    -->\n    <button\n      type=\"button\"\n      (click)=\"onNotificationClick(item)\"\n      [attr.aria-label]=\"item.title\"\n      class=\"absolute inset-0 outline-none\n             focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500\"\n    ></button>\n\n    @if (!item.read) {\n      <span class=\"absolute left-0 top-3 bottom-3 w-0.5 rounded-full bg-primary-500\"></span>\n    }\n\n    <!-- `pointer-events-none` so this `relative` block, which paints above the\n         overlay button, does not leave the avatar a dead spot in a clickable row. -->\n    <div class=\"relative shrink-0 mt-0.5 pointer-events-none\">\n      @if (item.actor?.avatar) {\n        <img [src]=\"item.actor!.avatar\" [alt]=\"item.actor!.name\"\n             class=\"w-10 h-10 rounded-full object-cover ring-2 ring-white dark:ring-gray-900\"/>\n      } @else if (item.actor) {\n        <div class=\"w-10 h-10 rounded-full flex items-center justify-center\n                    text-white text-sm font-semibold ring-2 ring-white dark:ring-gray-900\"\n             [class]=\"typeColor(item.type)\">\n          {{ initials(item.actor.name) }}\n        </div>\n      } @else {\n        <div class=\"w-10 h-10 rounded-xl flex items-center justify-center text-white\"\n             [class]=\"typeColor(item.type)\">\n          <sv-icon [name]=\"typeIcon(item.type)\" cssClass=\"w-5 h-5\" [strokeWidth]=\"2\" />\n        </div>\n      }\n      @if (item.actor) {\n        <span class=\"absolute -bottom-1 -right-1 w-5 h-5 rounded-full flex items-center justify-center\n                     text-white ring-2 ring-white dark:ring-gray-900\"\n              [class]=\"typeColor(item.type)\">\n          <sv-icon [name]=\"typeIcon(item.type)\" cssClass=\"w-2.5 h-2.5\" [strokeWidth]=\"2.5\" />\n        </span>\n      }\n    </div>\n\n    <div class=\"flex-1 min-w-0 pr-4\">\n      <div class=\"flex items-start justify-between gap-3 mb-0.5\">\n        <p class=\"text-[13.5px] leading-snug text-gray-900 dark:text-gray-100\"\n           [class.font-semibold]=\"!item.read\"\n           [class.font-normal]=\"item.read\">\n          {{ item.title }}\n        </p>\n        <div class=\"flex items-center gap-1.5 shrink-0 mt-0.5\">\n          @if (item.priority && item.priority !== 'low') {\n            <span class=\"w-2 h-2 rounded-full shrink-0\" [class]=\"priorityColor(item.priority!)\"></span>\n          }\n          <span class=\"text-[11px] text-gray-500 dark:text-gray-400 whitespace-nowrap\">{{ timeAgo(item.timestamp) }}</span>\n        </div>\n      </div>\n\n      @if (item.body) {\n        <p class=\"text-xs text-gray-500 dark:text-gray-400 leading-relaxed line-clamp-2 mt-0.5\">\n          {{ item.body }}\n        </p>\n      }\n\n      @if (item.target) {\n        <p class=\"text-xs text-primary-600 dark:text-primary-400 mt-1 truncate font-medium\">\n          {{ item.target.label }}\n        </p>\n      }\n\n      @if (item.actions && item.actions.length > 0) {\n        <div class=\"relative z-10 flex flex-wrap gap-2 mt-2.5\" svStopPropagation>\n          @for (action of item.actions; track action.id) {\n            <button type=\"button\"\n                    (click)=\"onActionClick(item, action.id, $event)\"\n                    class=\"px-3 py-1.5 rounded-lg text-xs font-medium\n                           transition-colors focus-visible:outline-none focus-visible:ring-2\"\n                    [class]=\"action.variant === 'primary'\n                      ? 'bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500'\n                      : action.variant === 'danger'\n                      ? 'bg-danger-50 dark:bg-danger-900/30 text-danger-700 dark:text-danger-400 hover:bg-danger-100 dark:hover:bg-danger-900/50 focus-visible:ring-danger-500'\n                      : 'bg-gray-100 dark:bg-gray-700/80 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 focus-visible:ring-gray-400'\">\n              {{ action.label }}\n            </button>\n          }\n        </div>\n      }\n\n      <ng-container [ngTemplateOutlet]=\"itemStateTpl\"\n                    [ngTemplateOutletContext]=\"{ item }\" />\n    </div>\n\n    <button type=\"button\"\n            (click)=\"onDismiss(item, $event)\"\n            aria-label=\"Dismiss notification\"\n            class=\"absolute top-3 right-3 z-10 w-6 h-6 flex items-center justify-center rounded-lg\n                   text-gray-300 dark:text-gray-600\n                   hover:text-gray-500 dark:hover:text-gray-400\n                   hover:bg-gray-100 dark:hover:bg-gray-700\n                   opacity-0 group-hover:opacity-100 transition-opacity\n                   focus-visible:outline-none focus-visible:opacity-100\">\n      <sv-icon name=\"close\" cssClass=\"w-3.5 h-3.5\" [strokeWidth]=\"2.5\" />\n    </button>\n  </div>\n</ng-template>\n\n<!-- ─── Shared: one row's async state (OF-10-M2) ─────────────────────────── -->\n<!--\n  ★★★ PER ROW, NOT ONE BANNER. The `error` input above is for the FEED — \"could\n      not load notifications\" — and routing a single row's failed write there\n      says the whole list is broken. Optimistic dismissal already rolls back\n      correctly; what was missing is anyone telling the user it rolled back, so\n      the row silently reappeared and they clicked again.\n\n  ★★ `role=\"alert\"`, not `status`: something the user asked for did not happen,\n      and it happened because of their click. The same line the audit draws\n      between `sv-result`'s error and its success.\n\n  ★ The retry sits on `z-10` with `svStopPropagation`, like the row's action\n    buttons, because the whole row is one big overlay <button> — see the note\n    in `#notifRow`. Without both, pressing it would open the notification.\n-->\n<ng-template #itemStateTpl let-item=\"item\">\n  @if (errorFor(item); as message) {\n    <div\n      role=\"alert\"\n      data-sv-state=\"item-error\"\n      class=\"relative z-10 mt-2 flex items-start gap-2 rounded-lg bg-danger-50 px-2.5 py-1.5\n             text-[11px] text-danger-800 dark:bg-danger-950/40 dark:text-danger-200\"\n      svStopPropagation\n    >\n      <span class=\"min-w-0 flex-1\">{{ message }}</span>\n      <button\n        type=\"button\"\n        data-sv-item-retry\n        class=\"shrink-0 font-semibold underline underline-offset-2 hover:no-underline\n               focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-danger-500\"\n        (click)=\"onRetryItem(item)\"\n      >Try again</button>\n    </div>\n  }\n</ng-template>\n\n<!-- ─── Shared: empty state ──────────────────────────────────────────────── -->\n<!-- The shared \"nothing in the list\" block. Every variant renders this when\n     filteredItems() is empty, so all three async states land in one place. -->\n<ng-template #emptyTpl>\n  @if (loading()) {\n    <div class=\"px-4 py-6\" data-sv-state=\"loading\">\n      <sv-skeleton-shell variant=\"list\" [rows]=\"4\" ariaLabel=\"Loading notifications\" />\n    </div>\n  } @else if (error()) {\n    <sv-error-shell\n      [error]=\"error()\"\n      title=\"Could not load notifications\"\n      (retry)=\"retry.emit()\"\n      data-sv-state=\"error\"\n    >\n      <sv-icon svShellIcon name=\"alert-triangle\" cssClass=\"w-8 h-8\" [strokeWidth]=\"1.5\" />\n    </sv-error-shell>\n  } @else {\n    <!-- \"You have no notifications\" and \"none match this filter\" are different\n         facts. Telling someone with 400 notifications that they are all caught\n         up, because they mistyped a search term, reads as a bug. -->\n    <sv-empty-shell\n      [reason]=\"isFiltered() ? 'no-results' : 'no-data'\"\n      [title]=\"isFiltered() ? 'No matching notifications' : 'You are all caught up'\"\n      [message]=\"isFiltered() ? 'Try a different search or tab.' : 'New notifications will appear here.'\"\n      [actionLabel]=\"isFiltered() ? 'Clear filters' : ''\"\n      (action)=\"clearFilters()\"\n      data-sv-state=\"empty\"\n    >\n      <sv-icon svShellIcon name=\"bell\" cssClass=\"w-8 h-8\" [strokeWidth]=\"1.5\" />\n    </sv-empty-shell>\n  }\n</ng-template>\n"],"mappings":"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"}