{"version":3,"file":"ActivityScreen-BvvE7UVU.mjs","names":[],"sources":["../src/shell/system-screen-slugs.ts","../src/screens/activity/use-infinite-activities.ts","../src/screens/activity/use-mark-activities-read-on-open.ts","../src/screens/activity/activity-feed-helpers.ts","../src/shell/AppLink.tsx","../src/screens/activity/activity-detail-links.tsx","../src/screens/activity/ActivityDetailCard.tsx","../src/screens/activity/PortalActivityFeed.tsx","../src/screens/ActivityScreen.tsx"],"sourcesContent":["/**\n * The system screen slugs, without the screens. system-screen-map.ts pairs\n * these with lazy-loaded components, so importing it from anything a screen\n * renders is a cycle — a component that links across screens (activity cards,\n * for one) reads this leaf instead. The map's keys are typed off this list, so\n * the two cannot drift.\n */\nexport const SYSTEM_SCREEN_SLUGS = [\n  \"profile\",\n  \"account\",\n  \"orders\",\n  \"subscriptions\",\n  \"messages\",\n  \"contacts\",\n  \"shop\",\n  \"customers\",\n  \"my-site\",\n  \"share/products\",\n  \"share/product\",\n  \"share/media\",\n  \"share/playlists\",\n  \"share/playlist\",\n  \"share/files\",\n  \"share/pages\",\n  \"share/page\",\n  \"app-download\",\n  \"activity\",\n] as const;\n\nexport type SystemScreenSlug = (typeof SYSTEM_SCREEN_SLUGS)[number];\n\n/**\n * Slugs of the screens a portal must opt into by adding them to its active\n * navigation; every other system screen renders regardless. The map derives\n * OPT_IN_SYSTEM_SCREENS from this set, so the two gates stay one decision.\n */\nexport const OPT_IN_SYSTEM_SCREEN_SLUGS: ReadonlySet<SystemScreenSlug> =\n  new Set([\"messages\", \"contacts\"] as const);\n\n/** Pre-sorted for greedy prefix matching (longest first). */\nconst SORTED_SCREEN_SLUGS = [...SYSTEM_SCREEN_SLUGS].sort(\n  (a, b) => b.length - a.length,\n);\n\n/** Prefix-match a slug against the system slugs, longest match first. */\nexport function findSystemSlugByPrefix(\n  slug: string,\n): SystemScreenSlug | undefined {\n  for (const key of SORTED_SCREEN_SLUGS) {\n    if (slug === key || slug.startsWith(key + \"/\")) {\n      return key;\n    }\n  }\n  return undefined;\n}\n","import {\n  useInfiniteQuery,\n  type InfiniteData,\n  type UseInfiniteQueryResult,\n} from \"@tanstack/react-query\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport type {\n  ActivitiesPage,\n  ActivityActorFilter,\n  FetchActivitiesArgs,\n} from \"@fluid-app/portal-core/widgets-api\";\n\n/**\n * Matches the API's default page size. Kept explicit so the request the screen\n * makes is the one the server was tuned for.\n */\nexport const ACTIVITY_PAGE_SIZE = 25;\n\n/**\n * Prefix shared by every filter combination of this feed. Anything that changes\n * the feed refreshes this rather than one key, so pages cached under a filter the\n * member is not currently looking at do not come back stale.\n */\nexport const ACTIVITY_FEED_QUERY_ROOT = [\"portal-sdk\", \"activities\"] as const;\n\nexport type UseInfiniteActivitiesParams = {\n  /** Rolling window in days; omit for all time. */\n  daysAgo?: number;\n  /** Activity slugs to filter to; omit or empty for every type. */\n  slugs?: string[];\n  /** Free-text term; omit or blank for no search. */\n  query?: string;\n  /** Earliest whole day, `YYYY-MM-DD`. */\n  from?: string;\n  /** Latest whole day, `YYYY-MM-DD`. */\n  to?: string;\n  /** IANA zone `from` and `to` are days in; omit to leave the server on UTC. */\n  timeZone?: string;\n  /** Restrict to contacts or to visitors; omit for both. */\n  actorType?: ActivityActorFilter;\n};\n\n/**\n * Cursor-paginated activity feed for the signed-in member.\n *\n * The query key includes a sorted slug list so re-picking the same filters in a\n * different order reuses the cached pages instead of refetching.\n */\nexport function useInfiniteActivities(\n  params: UseInfiniteActivitiesParams = {},\n): UseInfiniteQueryResult<InfiniteData<ActivitiesPage>, Error> {\n  const widgetsApi = useWidgetsApi();\n  const slugs = params.slugs?.length ? [...params.slugs].sort() : undefined;\n\n  return useInfiniteQuery({\n    queryKey: [\n      ...ACTIVITY_FEED_QUERY_ROOT,\n      \"infinite\",\n      params.daysAgo ?? null,\n      slugs?.join(\",\") ?? null,\n      params.query?.trim() || null,\n      params.from ?? null,\n      params.to ?? null,\n      // In the key because it changes which rows the same from/to select.\n      params.timeZone ?? null,\n      params.actorType ?? null,\n    ] as const,\n    queryFn: ({ pageParam, signal }) => {\n      // Built key by key: exactOptionalPropertyTypes rejects an explicit\n      // undefined for these optional fields.\n      const args: FetchActivitiesArgs = { limit: ACTIVITY_PAGE_SIZE };\n      if (params.daysAgo != null) args.daysAgo = params.daysAgo;\n      if (slugs) args.slugs = slugs;\n      // Trimmed, so trailing whitespace does not become a second cache entry for\n      // the same search.\n      const query = params.query?.trim();\n      if (query) args.query = query;\n      if (params.from != null) args.from = params.from;\n      if (params.to != null) args.to = params.to;\n      if (params.timeZone != null) args.timeZone = params.timeZone;\n      if (params.actorType != null) args.actorType = params.actorType;\n      if (pageParam != null) args.cursor = pageParam;\n      return widgetsApi.fetchActivities(args, signal);\n    },\n    getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,\n    initialPageParam: undefined as string | undefined,\n  });\n}\n","import { useEffect, useRef } from \"react\";\nimport {\n  useMutation,\n  useQueryClient,\n  type InfiniteData,\n} from \"@tanstack/react-query\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport type { ActivitiesPage } from \"@fluid-app/portal-core/widgets-api\";\nimport { ACTIVITY_FEED_QUERY_ROOT } from \"./use-infinite-activities\";\n\n/**\n * Stamps the cached rows read instead of invalidating: the endpoint marked the\n * whole feed, and a refetch would cancel any fetchNextPage in flight. Every\n * filter combination is stamped so pages cached under another filter do not\n * come back unread. The timestamp is the client's; only its null-ness is read.\n */\nfunction markCachedRowsRead(\n  queryClient: ReturnType<typeof useQueryClient>,\n): void {\n  const readAt = new Date().toISOString();\n\n  queryClient.setQueriesData<InfiniteData<ActivitiesPage>>(\n    { queryKey: ACTIVITY_FEED_QUERY_ROOT },\n    (cached) =>\n      cached && {\n        ...cached,\n        pages: cached.pages.map((page) => ({\n          ...page,\n          // Zero the count too so reopening the screen sees nothing unread and\n          // skips a repeat write.\n          ...(page.unreadCount !== undefined && { unreadCount: 0 }),\n          activities: page.activities.map((activity) =>\n            activity.readAt === null ? { ...activity, readAt } : activity,\n          ),\n        })),\n      },\n  );\n}\n\n/**\n * Marks the whole feed read once the screen has shown it; skipped when the\n * feed reports nothing unread. Waits for `feedLoaded` because firing on mount\n * races the screen's first fetch and the rows that arrive would still show as\n * unread.\n */\nexport function useMarkActivitiesReadOnOpen(\n  feedLoaded: boolean,\n  unreadCount: number | undefined,\n): void {\n  const widgetsApi = useWidgetsApi();\n  const queryClient = useQueryClient();\n\n  const { mutate } = useMutation({\n    mutationFn: () => widgetsApi.markActivitiesRead(),\n    onSuccess: (result) => {\n      // Only an explicit already-read means the cache needs no update; an\n      // adapter that cannot tell still stamps.\n      if (result.outcome === \"already-read\") return;\n\n      markCachedRowsRead(queryClient);\n    },\n    // console.error rather than a reporter: this package takes no Sentry\n    // dependency; the host app's console integration forwards this.\n    onError: (error) => {\n      console.error(\"[ActivityFeed] failed to mark activities read:\", error);\n    },\n  });\n\n  // The ref holds this to one write across StrictMode's dev remount. Only a\n  // reported zero skips the write: undefined means the adapter could not say,\n  // and treating it as zero would leave rows unread.\n  const hasNothingToMark = unreadCount === 0;\n\n  const hasMarked = useRef(false);\n  useEffect(() => {\n    if (!feedLoaded || hasNothingToMark || hasMarked.current) return;\n    hasMarked.current = true;\n    mutate();\n  }, [feedLoaded, hasNothingToMark, mutate]);\n}\n","import {\n  describeActor,\n  formatActivityDateHeader,\n  getActivityDateKey,\n  type ActivityDayLabels,\n} from \"@fluid-app/portal-core/activity-presentation\";\nimport type { Activity } from \"@fluid-app/portal-core/widgets-api-types\";\nimport type { ShellDict } from \"@fluid-app/portal-core/shell-translation-dictionary\";\n\nexport type ActivityTimeframe =\n  | \"all\"\n  | \"today\"\n  | \"yesterday\"\n  | \"last7\"\n  | \"custom\";\n\nexport const TIMEFRAMES = [\n  { value: \"all\", labelKey: \"activity_timeframe_all\" },\n  { value: \"today\", labelKey: \"activity_timeframe_today\" },\n  { value: \"yesterday\", labelKey: \"activity_timeframe_yesterday\" },\n  { value: \"last7\", labelKey: \"activity_timeframe_7\" },\n  { value: \"custom\", labelKey: \"activity_timeframe_custom\" },\n] as const satisfies ReadonlyArray<{\n  value: ActivityTimeframe;\n  labelKey: keyof ShellDict;\n}>;\n\n/**\n * Timeframes that need no explicit date range. Only the portal-tenant API\n * takes from/to bounds; the legacy adapter behind fluid-admin's portal preview\n * serves only days_ago, so it offers just these two.\n */\nconst RANGE_FREE_TIMEFRAMES: ReadonlyArray<ActivityTimeframe> = [\n  \"all\",\n  \"last7\",\n];\n\n/**\n * The timeframe options to offer for a given adapter. Exported for direct\n * testing: Radix mounts menu contents only when opened, so tests cannot read\n * the options from the DOM.\n */\nexport function timeframesFor(\n  supportsRange: boolean,\n): ReadonlyArray<(typeof TIMEFRAMES)[number]> {\n  if (supportsRange) return TIMEFRAMES;\n\n  return TIMEFRAMES.filter((frame) =>\n    RANGE_FREE_TIMEFRAMES.includes(frame.value),\n  );\n}\n\n/**\n * The server rejects terms shorter than this, so the feed holds the request\n * back instead of round-tripping to a 422.\n */\nexport const MIN_SEARCH_LENGTH = 2;\n\n/**\n * The server rejects queries longer than this. Applies to the whole string,\n * unlike the minimum, which is per term.\n */\nexport const MAX_SEARCH_LENGTH = 100;\n\nexport const SEARCH_DEBOUNCE_MS = 300;\n/** A resolved date window, as the `YYYY-MM-DD` strings the API takes. */\nexport type ActivityDateRange = { from?: string; to?: string };\n\n/**\n * What a timeframe resolves to on the wire: either a rolling window in days or\n * an explicit range. Never both, which the server rejects.\n */\nexport type ActivityWindow = ActivityDateRange & { daysAgo?: number };\n\n/**\n * Stays a rolling days_ago window: the legacy adapter serves days_ago but not\n * explicit date ranges, so folding this into from/to bounds would break it\n * there.\n */\nconst LAST_7_DAYS_AGO = 7;\n\n/**\n * The local calendar day `at` falls on, as `YYYY-MM-DD`. Built from local date\n * fields rather than sliced off an ISO string (that gives the UTC day), so it\n * matches `getActivityDateKey`, which the day headers group by.\n */\nexport function localDay(at: Date): string {\n  const year = at.getFullYear();\n  const month = String(at.getMonth() + 1).padStart(2, \"0\");\n  const day = String(at.getDate()).padStart(2, \"0\");\n\n  return `${year}-${month}-${day}`;\n}\n\n/**\n * The viewer's IANA zone, for the server to read `from` and `to` in.\n *\n * Undefined when the environment cannot name it, which leaves the server on UTC\n * rather than guessing. An offset would not do: the offset for a past date is not\n * necessarily today's across a daylight-saving change.\n */\nexport function viewerTimeZone(): string | undefined {\n  return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;\n}\n\n/**\n * The next local midnight strictly after `atMs`, so a caller that reschedules\n * on the result always moves forward instead of firing in a loop.\n */\nexport function nextLocalMidnight(atMs: number): number {\n  const at = new Date(atMs);\n\n  return new Date(\n    at.getFullYear(),\n    at.getMonth(),\n    at.getDate() + 1,\n    0,\n    0,\n    0,\n    0,\n  ).getTime();\n}\n\n/**\n * Turn a chosen timeframe into the window to request.\n *\n * A single day needs explicit bounds, since `days_ago` has no upper bound and so\n * cannot say \"yesterday\". `custom` passes the caller's own bounds through, half-open\n * ones included.\n */\nexport function resolveTimeframe(\n  timeframe: ActivityTimeframe,\n  nowMs: number,\n  custom: ActivityDateRange = {},\n): ActivityWindow {\n  const dayMs = 24 * 60 * 60 * 1000;\n  const today = localDay(new Date(nowMs));\n\n  switch (timeframe) {\n    case \"today\":\n      return { from: today, to: today };\n    case \"yesterday\": {\n      const yesterday = localDay(new Date(nowMs - dayMs));\n      return { from: yesterday, to: yesterday };\n    }\n    case \"last7\":\n      return { daysAgo: LAST_7_DAYS_AGO };\n    case \"custom\": {\n      // The date inputs allow typing an inverted pair, which the server\n      // rejects. Collapse it to a single day rather than dropping both bounds,\n      // so the window never silently widens to all time while the menu still\n      // says Custom range.\n      if (custom.from && custom.to && custom.to < custom.from) {\n        return { from: custom.from, to: custom.from };\n      }\n\n      return {\n        ...(custom.from ? { from: custom.from } : {}),\n        ...(custom.to ? { to: custom.to } : {}),\n      };\n    }\n    case \"all\":\n      return {};\n    default:\n      return assertNoUnhandledTimeframe(timeframe);\n  }\n}\n\n/** A new ActivityTimeframe has to be handled above rather than mean \"no filter\". */\nfunction assertNoUnhandledTimeframe(timeframe: never): ActivityWindow {\n  void timeframe;\n\n  return {};\n}\n\n/**\n * Whether the query fits the server's limits. The minimum applies to each\n * whitespace-separated term, while the maximum applies to the whole string,\n * so one short word makes the whole query invalid.\n */\nexport function isSearchable(\n  query: string,\n  minTermLength: number,\n  maxQueryLength: number,\n): boolean {\n  if (query.length > maxQueryLength) return false;\n\n  const terms = query.split(/\\s+/).filter(Boolean);\n\n  return (\n    terms.length > 0 && terms.every((term) => term.length >= minTermLength)\n  );\n}\n\nexport type ActivityDayGroup = {\n  /** Local date key, stable across renders — used as the React key. */\n  key: string;\n  label: string;\n  activities: Activity[];\n};\n\n/**\n * Buckets a chronological (newest-first) feed into day groups, preserving the\n * server's order within and between groups.\n *\n * Grouping keys off the local date derived from each timestamp rather than any\n * server-formatted string, so a rep sees days as their own clock reports them.\n */\nexport function groupActivitiesByDay(\n  activities: readonly Activity[],\n  nowMs: number,\n  labels: ActivityDayLabels,\n  locale?: string,\n): ActivityDayGroup[] {\n  const groups: ActivityDayGroup[] = [];\n  const byKey = new Map<string, ActivityDayGroup>();\n\n  for (const activity of activities) {\n    const key = getActivityDateKey(activity.timestamp);\n    const existing = byKey.get(key);\n    if (existing) {\n      existing.activities.push(activity);\n      continue;\n    }\n\n    const group: ActivityDayGroup = {\n      key,\n      label: formatActivityDateHeader(\n        activity.timestamp,\n        nowMs,\n        labels,\n        locale,\n      ),\n      activities: [activity],\n    };\n    byKey.set(key, group);\n    groups.push(group);\n  }\n\n  return groups;\n}\n\n/**\n * Where a row sits in the feed, which decides how much of the timeline spine it\n * draws: the first row starts the line below its avatar, the last row stops at\n * its avatar, and a lone row draws none.\n */\nexport type SpinePosition = \"only\" | \"first\" | \"middle\" | \"last\";\n\n/**\n * Callers pass a feed-wide index and total, not per-day ones: the spine is one\n * continuous line through the date headers, and counting per day broke it at\n * every group boundary.\n */\nexport function spinePositionAt(index: number, total: number): SpinePosition {\n  if (total <= 1) return \"only\";\n  if (index === 0) return \"first\";\n  if (index === total - 1) return \"last\";\n  return \"middle\";\n}\n\n/**\n * How many rows precede each day group, so a row can be placed against the whole\n * feed. Index i is the running total before group i.\n */\nexport function rowOffsetsByGroup(\n  groups: ReadonlyArray<ActivityDayGroup>,\n): number[] {\n  let running = 0;\n\n  return groups.map((group) => {\n    const offset = running;\n    running += group.activities.length;\n    return offset;\n  });\n}\n\nexport { describeActor };\n\n/**\n * Moving one bound past the other carries the other with it.\n *\n * `min` and `max` on a date input only mark it invalid; they do not stop a typed\n * value. Collapsing to a single day keeps every edit a range the server accepts.\n */\nexport function withFrom(\n  range: ActivityDateRange,\n  from: string,\n): ActivityDateRange {\n  const to = range.to && from && range.to < from ? from : range.to;\n\n  return { ...(from && { from }), ...(to && { to }) };\n}\n\nexport function withTo(\n  range: ActivityDateRange,\n  to: string,\n): ActivityDateRange {\n  const from = range.from && to && to < range.from ? to : range.from;\n\n  return { ...(from && { from }), ...(to && { to }) };\n}\n","import React, {\n  forwardRef,\n  type AnchorHTMLAttributes,\n  type MouseEvent,\n} from \"react\";\nimport { useAppNavigation } from \"./AppNavigationContext\";\n\nexport interface AppLinkProps extends Omit<\n  AnchorHTMLAttributes<HTMLAnchorElement>,\n  \"href\"\n> {\n  /** Slug to navigate to (e.g. \"contacts/123\") */\n  to: string;\n}\n\n/**\n * SPA-aware link that renders a real `<a href>` for accessibility\n * (right-click, ctrl+click, screen readers) but intercepts normal\n * clicks for client-side navigation.\n */\nexport const AppLink: React.ForwardRefExoticComponent<\n  AppLinkProps & React.RefAttributes<HTMLAnchorElement>\n> = forwardRef<HTMLAnchorElement, AppLinkProps>(function AppLink(\n  { to, onClick, children, ...rest },\n  ref,\n) {\n  const { navigate, buildHref } = useAppNavigation();\n\n  const handleClick = (e: MouseEvent<HTMLAnchorElement>) => {\n    // Allow custom onClick handlers to run first\n    onClick?.(e);\n    if (e.defaultPrevented) return;\n\n    // Skip SPA navigation for modifier keys, non-primary button, or external target\n    if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n    if (e.button !== 0) return;\n    if (rest.target && rest.target !== \"_self\") return;\n\n    e.preventDefault();\n    navigate(to);\n  };\n\n  return (\n    <a ref={ref} href={buildHref(to)} onClick={handleClick} {...rest}>\n      {children}\n    </a>\n  );\n});\n","import type { JSX, ReactNode } from \"react\";\nimport type { ActivityDetails } from \"@fluid-app/portal-core/widgets-api-types\";\nimport type { NavigationItem } from \"../../types/navigation\";\nimport { AppLink } from \"../../shell/AppLink\";\nimport { useOptionalAppNavigation } from \"../../shell/AppNavigationContext\";\nimport {\n  OPT_IN_SYSTEM_SCREEN_SLUGS,\n  findSystemSlugByPrefix,\n} from \"../../shell/system-screen-slugs\";\nimport { buildMessagingSlugFromRoute } from \"../messaging-route-state\";\n\n/**\n * The portal screen that owns each kind of record a payload can point at.\n *\n * Several details name their subject polymorphically, and the type decides where it\n * opens. A Share can also point at an EnrollmentPack, Promotion, UserCompany or\n * Simulator; none has a portal screen, so anything missing here does not link.\n */\nconst SCREEN_FOR_RECORD: Record<string, string> = {\n  Product: \"shop\",\n  Medium: \"share/media\",\n  Library: \"share/playlists\",\n};\n\n/** Base slug of the messaging screen. See the `message` case for why not \"messaging\". */\nconst MESSAGING_SLUG = \"messages\";\n\n/** Base slug of the contacts screen, for linking a row's actor. */\nexport const CONTACTS_SLUG = \"contacts\";\n\nfunction slugForRecord(\n  recordType: string | undefined,\n  recordId: number | undefined,\n): string | null {\n  if (!recordType || !recordId) return null;\n\n  const screen = SCREEN_FOR_RECORD[recordType];\n\n  return screen ? `${screen}/${recordId}` : null;\n}\n\n/**\n * Where a card can send a rep inside the portal, or null when nowhere can.\n *\n * Contacts are linked from the row's actor instead. Orders are the one subject with a\n * screen we cannot open: PortalTenant::OrderFinder scopes to the viewing member's own\n * customer, so a contact's order is not theirs to open.\n */\nexport function portalDetailSlug(details: ActivityDetails): string | null {\n  switch (details.type) {\n    // Both name their subject outright rather than polymorphically.\n    case \"cart\":\n      return slugForRecord(\"Product\", details.productId);\n    case \"video\":\n      return slugForRecord(\"Medium\", details.mediumId);\n\n    // Built rather than written out, because messaging owns the shape and encodes\n    // each segment. The base is \"messages\", the slug SYSTEM_SLUG_SCREEN_MAP routes to\n    // MessagingScreen and the one real portals carry in their nav. Its page template\n    // registers \"messaging\" instead, which routes nowhere.\n    case \"message\":\n      return details.conversationId && details.messageId\n        ? buildMessagingSlugFromRoute(MESSAGING_SLUG, {\n            view: \"message\",\n            conversationId: details.conversationId,\n            messageId: String(details.messageId),\n          })\n        : null;\n\n    case \"pageView\":\n      return slugForRecord(details.resourceType, details.resourceId);\n    case \"review\":\n      return slugForRecord(details.reviewableType, details.reviewableId);\n    case \"reaction\":\n      return slugForRecord(details.reactableType, details.reactableId);\n    case \"comment\":\n      return slugForRecord(details.relateableType, details.relateableId);\n\n    default:\n      return null;\n  }\n}\n\nfunction navSlugs(items: readonly NavigationItem[]): string[] {\n  return items.flatMap((item) => [\n    ...(item.slug ? [item.slug] : []),\n    ...navSlugs(item.children ?? []),\n  ]);\n}\n\n/**\n * Whether this portal would render the slug, mirroring PageRouter so a link never\n * offers a Page Not Found.\n *\n * Messaging and contacts are opt-in there, rendering only when the tenant's nav\n * carries a slug for them. Every other system screen renders regardless.\n */\nfunction isReachable(slug: string, items: readonly NavigationItem[]): boolean {\n  const base = findSystemSlugByPrefix(slug);\n  if (!base) return false;\n  if (!OPT_IN_SYSTEM_SCREEN_SLUGS.has(base)) return true;\n\n  return navSlugs(items).some(\n    (navSlug) => findSystemSlugByPrefix(navSlug) === base,\n  );\n}\n\n/**\n * Whether a link to `to` would actually go somewhere, for callers that style the\n * affordance themselves. Shared with PortalDetailLink rather than reimplemented,\n * since contacts and messaging are opt-in: the same slug links in one portal and\n * not in another, and a name painted as a link that does nothing is worse than a\n * plain one.\n */\nexport function usePortalDetailReachable(to: string | null): boolean {\n  const navigation = useOptionalAppNavigation();\n\n  if (!to || !navigation) return false;\n\n  return isReachable(to, navigation.navItems);\n}\n\n/**\n * Wraps its children in a portal link, or leaves them alone when the link would\n * not resolve — no shell to navigate within, which is the case in tests and the\n * builder's preview, or a screen this portal does not offer.\n */\nexport function PortalDetailLink({\n  to,\n  className,\n  children,\n}: {\n  to: string | null;\n  className?: string;\n  children: ReactNode;\n}): JSX.Element {\n  const reachable = usePortalDetailReachable(to);\n\n  if (!to || !reachable) return <>{children}</>;\n\n  return (\n    <AppLink to={to} className={className}>\n      {children}\n    </AppLink>\n  );\n}\n","import type { JSX } from \"react\";\nimport { useState } from \"react\";\nimport { ImageOff, Star } from \"lucide-react\";\nimport type { ActivityDetails } from \"@fluid-app/portal-core/widgets-api-types\";\nimport type { ShellT } from \"./activity-translation\";\nimport { PortalDetailLink, portalDetailSlug } from \"./activity-detail-links\";\n\ntype ActivityDetailCardProps = {\n  details: ActivityDetails;\n  t: ShellT;\n};\n\n/**\n * The rich body under an activity row, one shape per detail type.\n *\n * Every field is optional — the server strips blanks before storing a detail —\n * so each branch renders only what arrived and returns null when nothing\n * meaningful is left. The row above it already carries who and when.\n */\nexport function ActivityDetailCard({\n  details,\n  t,\n}: ActivityDetailCardProps): JSX.Element | null {\n  switch (details.type) {\n    case \"order\": {\n      const total = details.orderTotalInCurrency ?? details.orderTotal;\n      if (details.orderItemName || details.imageUrl) {\n        const extraItems = (details.orderItemCount ?? 0) - 1;\n        const subtitle = [\n          total,\n          extraItems > 0\n            ? t(\"activity_order_more_items\", { count: extraItems })\n            : null,\n        ]\n          .filter(Boolean)\n          .join(\" · \");\n        return (\n          <MediaCard\n            title={details.orderItemName}\n            imageUrl={details.imageUrl}\n            subtitle={subtitle || undefined}\n          />\n        );\n      }\n      return total ? (\n        <MetaCard label={t(\"activity_order_total\")} value={total} />\n      ) : null;\n    }\n\n    case \"video\":\n      return details.mediumTitle || details.imageUrl ? (\n        <PortalDetailLink to={portalDetailSlug(details)} className=\"block\">\n          <MediaCard title={details.mediumTitle} imageUrl={details.imageUrl} />\n        </PortalDetailLink>\n      ) : null;\n\n    case \"cart\":\n      return details.productName || details.imageUrl ? (\n        <PortalDetailLink to={portalDetailSlug(details)} className=\"block\">\n          <MediaCard title={details.productName} imageUrl={details.imageUrl} />\n        </PortalDetailLink>\n      ) : null;\n\n    // Prefer the in-portal product page over the public URL when both resolve:\n    // it keeps the rep in the portal on a page they can act on.\n    case \"pageView\": {\n      const to = portalDetailSlug(details);\n      const label = details.resourceName ?? details.url;\n\n      if (to && label) {\n        return (\n          <PortalDetailLink to={to} className={LINK_CLASS}>\n            {label}\n          </PortalDetailLink>\n        );\n      }\n\n      return details.url ? (\n        <PageLink label={label ?? details.url} url={details.url} />\n      ) : null;\n    }\n\n    // No card: the row's own sentence already carries the lead's type, reason,\n    // and interest.\n    case \"contactLead\":\n      return null;\n\n    case \"comment\":\n      return details.body ? (\n        <PortalDetailLink to={portalDetailSlug(details)} className=\"block\">\n          <QuoteCard body={details.body} caption={details.relateableName} />\n        </PortalDetailLink>\n      ) : null;\n\n    case \"review\":\n      return details.rating != null || details.body ? (\n        <PortalDetailLink to={portalDetailSlug(details)} className=\"block\">\n          <ReviewCard\n            rating={details.rating}\n            body={details.body}\n            caption={details.reviewableName}\n            t={t}\n          />\n        </PortalDetailLink>\n      ) : null;\n\n    case \"reaction\":\n      return details.icon ? (\n        <PortalDetailLink to={portalDetailSlug(details)} className=\"block\">\n          <ReactionCard icon={details.icon} caption={details.reactableTitle} />\n        </PortalDetailLink>\n      ) : null;\n\n    // A subject means the message was an email; it is the only visible\n    // difference from an SMS.\n    case \"message\":\n      return details.body || details.subject ? (\n        <PortalDetailLink to={portalDetailSlug(details)} className=\"block\">\n          <QuoteCard\n            heading={details.body ? details.subject : undefined}\n            body={details.body ?? details.subject ?? \"\"}\n          />\n        </PortalDetailLink>\n      ) : null;\n\n    default:\n      return null;\n  }\n}\n\nconst CARD_CLASS =\n  \"border-border bg-card mt-2 flex items-center gap-3 rounded-lg border p-2\";\n\nfunction MediaCard({\n  title,\n  imageUrl,\n  subtitle,\n}: {\n  title: string | undefined;\n  imageUrl: string | undefined;\n  subtitle?: string;\n}): JSX.Element {\n  return (\n    <div className={CARD_CLASS}>\n      <Thumbnail src={imageUrl} alt={title ?? \"\"} />\n      <div className=\"min-w-0 flex-1\">\n        {title && (\n          <p className=\"text-foreground truncate text-sm font-semibold\">\n            {title}\n          </p>\n        )}\n        {subtitle && (\n          <p className=\"text-muted-foreground truncate text-xs\">{subtitle}</p>\n        )}\n      </div>\n    </div>\n  );\n}\n\nfunction Thumbnail({\n  src,\n  alt,\n}: {\n  src: string | undefined;\n  alt: string;\n}): JSX.Element {\n  const [failed, setFailed] = useState(false);\n\n  if (!src || failed) {\n    return (\n      <div\n        aria-hidden=\"true\"\n        className=\"bg-muted text-muted-foreground flex h-[60px] w-[115px] shrink-0 items-center justify-center rounded\"\n      >\n        <ImageOff className=\"size-4\" />\n      </div>\n    );\n  }\n\n  return (\n    <img\n      src={src}\n      alt={alt}\n      loading=\"lazy\"\n      onError={() => setFailed(true)}\n      className=\"bg-muted h-[60px] w-[115px] shrink-0 rounded object-cover object-top\"\n    />\n  );\n}\n\nfunction MetaCard({\n  label,\n  value,\n}: {\n  label: string;\n  value: string;\n}): JSX.Element {\n  return (\n    <div className={CARD_CLASS}>\n      <div className=\"min-w-0 flex-1\">\n        <p className=\"text-muted-foreground text-xs font-medium\">{label}</p>\n        <p className=\"text-foreground truncate text-sm font-semibold\">\n          {value}\n        </p>\n      </div>\n    </div>\n  );\n}\n\nconst LINK_CLASS = \"text-primary mt-1 block truncate text-sm hover:underline\";\n\nfunction PageLink({ label, url }: { label: string; url: string }): JSX.Element {\n  return (\n    <a\n      href={url}\n      target=\"_blank\"\n      rel=\"noreferrer noopener\"\n      className={LINK_CLASS}\n    >\n      {label}\n    </a>\n  );\n}\n\nfunction QuoteCard({\n  heading,\n  body,\n  caption,\n}: {\n  heading?: string | undefined;\n  body: string;\n  caption?: string | undefined;\n}): JSX.Element {\n  return (\n    <div className={`${CARD_CLASS} flex-col items-stretch gap-1`}>\n      {heading && (\n        <p className=\"text-foreground truncate text-xs font-semibold\">\n          {heading}\n        </p>\n      )}\n      {/* whitespace-pre-line: an SMS body arrives with real newlines; the\n          server only flattens email bodies. */}\n      <p className=\"text-foreground line-clamp-3 text-sm leading-relaxed whitespace-pre-line\">\n        {body}\n      </p>\n      {caption && <p className=\"text-muted-foreground text-xs\">{caption}</p>}\n    </div>\n  );\n}\n\nfunction ReviewCard({\n  rating,\n  body,\n  caption,\n  t,\n}: {\n  rating: number | undefined;\n  body: string | undefined;\n  caption: string | undefined;\n  t: ShellT;\n}): JSX.Element {\n  return (\n    <div className={`${CARD_CLASS} flex-col items-stretch gap-1`}>\n      {rating != null && (\n        <div\n          className=\"flex items-center gap-0.5\"\n          aria-label={t(\"activity_rating\", { rating })}\n        >\n          {[1, 2, 3, 4, 5].map((star) => (\n            <Star\n              key={star}\n              aria-hidden=\"true\"\n              className={\n                star <= rating\n                  ? \"fill-primary text-primary size-3.5\"\n                  : \"text-muted-foreground size-3.5\"\n              }\n            />\n          ))}\n        </div>\n      )}\n      {body && (\n        <p className=\"text-foreground line-clamp-3 text-sm leading-relaxed\">\n          {body}\n        </p>\n      )}\n      {caption && <p className=\"text-muted-foreground text-xs\">{caption}</p>}\n    </div>\n  );\n}\n\nfunction ReactionCard({\n  icon,\n  caption,\n}: {\n  icon: string;\n  caption: string | undefined;\n}): JSX.Element {\n  return (\n    <div className={CARD_CLASS}>\n      <span className=\"text-xl leading-none\" aria-hidden=\"true\">\n        {icon}\n      </span>\n      {caption && (\n        <p className=\"text-foreground min-w-0 flex-1 truncate text-sm font-semibold\">\n          {caption}\n        </p>\n      )}\n    </div>\n  );\n}\n","import { type JSX, useEffect, useId, useMemo, useState, useRef } from \"react\";\nimport {\n  AlertCircle,\n  Calendar,\n  Filter,\n  ChevronDown,\n  Search,\n  X,\n  type LucideIcon,\n} from \"lucide-react\";\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n  Button,\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n  Tabs,\n  TabsList,\n  TabsTrigger,\n  Input,\n  Label,\n  Skeleton,\n} from \"@fluid-app/ui-primitives\";\nimport { ScreenHeaderBreadcrumbs } from \"@fluid-app/portal-react/shell/ScreenHeaderContext\";\nimport { useOptionalNavigationParent } from \"../../shell/use-navigation-parent\";\nimport { useShellTranslation } from \"@fluid-app/portal-core/shell-translation-api-context\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport {\n  ACTIVITY_CATEGORY_KEYS,\n  EmptyActivityIcon,\n  formatActivityTime,\n  getActivityIcon,\n  initialsForActor,\n  slugsForCategories,\n  type ActivityCategory,\n} from \"@fluid-app/portal-core/activity-presentation\";\nimport type { Activity } from \"@fluid-app/portal-core/widgets-api-types\";\nimport type { ShellDict } from \"@fluid-app/portal-core/shell-translation-dictionary\";\nimport { useInfiniteActivities } from \"./use-infinite-activities\";\nimport { useMarkActivitiesReadOnOpen } from \"./use-mark-activities-read-on-open\";\nimport {\n  MAX_SEARCH_LENGTH,\n  MIN_SEARCH_LENGTH,\n  SEARCH_DEBOUNCE_MS,\n  TIMEFRAMES,\n  describeActor,\n  groupActivitiesByDay,\n  isSearchable,\n  nextLocalMidnight,\n  localDay,\n  resolveTimeframe,\n  rowOffsetsByGroup,\n  spinePositionAt,\n  timeframesFor,\n  viewerTimeZone,\n  type ActivityDateRange,\n  type ActivityTimeframe,\n  type SpinePosition,\n  withFrom,\n  withTo,\n} from \"./activity-feed-helpers\";\nimport type { ActivityActorFilter } from \"@fluid-app/portal-core/widgets-api\";\nimport { ActivityDetailCard } from \"./ActivityDetailCard\";\nimport {\n  CONTACTS_SLUG,\n  PortalDetailLink,\n  usePortalDetailReachable,\n} from \"./activity-detail-links\";\nimport type { ShellT } from \"./activity-translation\";\n\n/**\n * Reps land here from the Recent Activity widget, whose count covers the last\n * 7 days, so the feed opens on the same window that count described.\n */\nconst DEFAULT_TIMEFRAME: ActivityTimeframe = \"last7\";\n\n/** `all` means the filter is off; it is never sent to the API. */\ntype ActorFilter = \"all\" | ActivityActorFilter;\n\nconst ACTOR_FILTERS = [\n  { value: \"all\", labelKey: \"activity_actor_filter_all\" },\n  { value: \"contact\", labelKey: \"activity_actor_filter_contacts\" },\n  { value: \"visitor\", labelKey: \"activity_actor_filter_visitors\" },\n] as const satisfies ReadonlyArray<{\n  value: ActorFilter;\n  labelKey: keyof ShellDict;\n}>;\n\nconst CATEGORY_LABEL_KEYS = {\n  orders: \"activity_category_orders\",\n  carts: \"activity_category_carts\",\n  messages: \"activity_category_messages\",\n  videos: \"activity_category_videos\",\n  pageViews: \"activity_category_page_views\",\n  leads: \"activity_category_leads\",\n  reviews: \"activity_category_reviews\",\n  reactions: \"activity_category_reactions\",\n} as const satisfies Record<ActivityCategory, string>;\n\nexport function PortalActivityFeed(): JSX.Element {\n  const { t, locale } = useShellTranslation();\n  // Not every adapter supports every filter — asking for one it refuses\n  // throws, which this screen renders as its generic error state. Only offer\n  // controls the port advertises.\n  const { dateRange: supportsRange, actorType: supportsActor } =\n    useWidgetsApi().activityFilterSupport;\n  const [timeframe, setTimeframe] =\n    useState<ActivityTimeframe>(DEFAULT_TIMEFRAME);\n  const [customRange, setCustomRange] = useState<ActivityDateRange>({});\n  const [categories, setCategories] = useState<ActivityCategory[]>([]);\n  const [actorFilter, setActorFilter] = useState<ActorFilter>(\"all\");\n\n  // The input is committed to searchTerm on a debounce so a request doesn't\n  // fire per keystroke.\n  const [searchInput, setSearchInput] = useState(\"\");\n  const navigationParent = useOptionalNavigationParent();\n  // Ids that arrived unread this visit. The screen marks everything read on\n  // load, so a dot driven by live readAt would disappear as soon as that\n  // request lands; this snapshot keeps the dots for the visit. A remount\n  // starts clean.\n  const firstSeenUnread = useRef<Set<number>>(new Set());\n  const [searchTerm, setSearchTerm] = useState(\"\");\n\n  const slugs = useMemo(() => slugsForCategories(categories), [categories]);\n  const timeframes = useMemo(\n    () => timeframesFor(supportsRange),\n    [supportsRange],\n  );\n\n  // Which day counts as \"Today\", for the group headers and the bounds relative\n  // timeframes resolve to. Advanced at each local midnight rather than ticked:\n  // the grouping memo stays stable, but a feed left open overnight doesn't\n  // keep filtering yesterday under a Today label.\n  const [nowMs, setNowMs] = useState(() => Date.now());\n\n  useEffect(() => {\n    const midnight = nextLocalMidnight(nowMs);\n    const timer = setTimeout(\n      () => setNowMs(midnight),\n      Math.max(midnight - Date.now(), 0),\n    );\n\n    return () => clearTimeout(timer);\n  }, [nowMs]);\n\n  const range = useMemo(\n    () => resolveTimeframe(timeframe, nowMs, customRange),\n    [timeframe, nowMs, customRange],\n  );\n\n  // Read once so a mid-session zone change can't churn the query key and look\n  // like a new filter.\n  const [timeZone] = useState(viewerTimeZone);\n\n  const trimmedInput = searchInput.trim();\n  const searchReady = isSearchable(\n    trimmedInput,\n    MIN_SEARCH_LENGTH,\n    MAX_SEARCH_LENGTH,\n  );\n  const searchInvalid = trimmedInput.length > 0 && !searchReady;\n\n  // An invalid term is never sent — the 422 would render as the error state.\n  // It also never clears the applied search: mid-edit, the current results\n  // stay put instead of snapping back to the unfiltered feed.\n  useEffect(() => {\n    if (searchInvalid) return;\n\n    const next = searchReady ? trimmedInput : \"\";\n    if (next === searchTerm) return;\n\n    const timer = setTimeout(() => setSearchTerm(next), SEARCH_DEBOUNCE_MS);\n    return () => clearTimeout(timer);\n  }, [trimmedInput, searchReady, searchInvalid, searchTerm]);\n\n  const {\n    data,\n    isLoading,\n    isError,\n    isSuccess,\n    refetch,\n    fetchNextPage,\n    hasNextPage,\n    isFetchingNextPage,\n  } = useInfiniteActivities({\n    ...(slugs.length > 0 && { slugs }),\n    ...(searchTerm && { query: searchTerm }),\n    ...(range.daysAgo != null && { daysAgo: range.daysAgo }),\n    ...(range.from && { from: range.from }),\n    ...(range.to && { to: range.to }),\n    // The zone the from/to days are in — without it the server reads them as\n    // UTC days, which may not match the local days the headers group by.\n    ...((range.from || range.to) && timeZone ? { timeZone } : {}),\n    ...(actorFilter !== \"all\" && { actorType: actorFilter }),\n  });\n\n  // Gated on the query succeeding: marking read before the first page lands\n  // cannot clear the dots (see the hook).\n  useMarkActivitiesReadOnOpen(isSuccess, data?.pages[0]?.unreadCount);\n\n  const activities = useMemo(\n    () => data?.pages.flatMap((page) => page.activities) ?? [],\n    [data],\n  );\n  // Every page reports the same total for a given query; the first is enough.\n  const totalCount = data?.pages[0]?.totalCount;\n  // During render rather than in an effect: the mark-read effect would stamp\n  // readAt before an effect here got to see the rows unread. Safe because\n  // Set.add is idempotent.\n  for (const activity of activities) {\n    if (activity.readAt === null) firstSeenUnread.current.add(activity.id);\n  }\n\n  const dayLabels = useMemo(\n    () => ({\n      today: t(\"activity_date_today\"),\n      yesterday: t(\"activity_date_yesterday\"),\n    }),\n    [t],\n  );\n  const groups = useMemo(\n    () => groupActivitiesByDay(activities, nowMs, dayLabels, locale),\n    [activities, nowMs, dayLabels, locale],\n  );\n  const rowOffsets = useMemo(() => rowOffsetsByGroup(groups), [groups]);\n  const totalRows = activities.length;\n\n  // Based on the resolved window, not the picked timeframe: \"Custom range\"\n  // with no dates narrows nothing, and the default 7-day window is not a\n  // filter to clear — an empty feed there just means no recent activity.\n  const isFiltered =\n    categories.length > 0 ||\n    range.from != null ||\n    range.to != null ||\n    actorFilter !== \"all\" ||\n    searchTerm.length > 0;\n\n  // Seeded with today rather than left empty: Safari paints a focused empty\n  // date input with today's segments, indistinguishable from a committed value.\n  const pickTimeframe = (next: ActivityTimeframe) => {\n    if (next === \"custom\" && !customRange.from && !customRange.to) {\n      const today = localDay(new Date(nowMs));\n      setCustomRange({ from: today, to: today });\n    }\n    setTimeframe(next);\n  };\n\n  const clearFilters = () => {\n    setCategories([]);\n    setTimeframe(DEFAULT_TIMEFRAME);\n    setCustomRange({});\n    setActorFilter(\"all\");\n    setSearchInput(\"\");\n    setSearchTerm(\"\");\n  };\n\n  const toggleCategory = (category: ActivityCategory) => {\n    setCategories((current) =>\n      current.includes(category)\n        ? current.filter((entry) => entry !== category)\n        : [...current, category],\n    );\n  };\n\n  return (\n    <>\n      <ScreenHeaderBreadcrumbs>\n        <Breadcrumb>\n          <BreadcrumbList className=\"text-lg\">\n            {/* This screen isn't in the nav, so this crumb is the only in-app\n                way back. */}\n            {navigationParent && (\n              <>\n                <BreadcrumbItem>\n                  <BreadcrumbLink\n                    href=\"#\"\n                    onClick={(event) => {\n                      event.preventDefault();\n                      navigationParent.onClick();\n                    }}\n                  >\n                    {navigationParent.label}\n                  </BreadcrumbLink>\n                </BreadcrumbItem>\n                <BreadcrumbSeparator />\n              </>\n            )}\n            <BreadcrumbItem>\n              <BreadcrumbPage className=\"font-semibold\">\n                {t(\"activity_title\")}\n              </BreadcrumbPage>\n            </BreadcrumbItem>\n          </BreadcrumbList>\n        </Breadcrumb>\n      </ScreenHeaderBreadcrumbs>\n\n      <div className=\"relative mx-auto max-w-4xl px-5 pt-8 pb-20 md:px-8\">\n        <div>\n          <h1 className=\"text-foreground truncate text-xl font-bold tracking-tight md:text-2xl\">\n            {t(\"activity_title\")}\n          </h1>\n          {/* Only shown for a windowed query — the API cannot count all time. */}\n          {totalCount != null && (\n            <p className=\"text-muted-foreground mt-0.5 text-xs\">\n              {t(\"activity_count\", { count: totalCount })}\n            </p>\n          )}\n        </div>\n\n        <div className=\"mt-5 flex items-center gap-3\">\n          {supportsActor && (\n            <ActorTabs value={actorFilter} onChange={setActorFilter} t={t} />\n          )}\n          <div className=\"ml-auto flex items-center gap-2\">\n            <SearchField\n              value={searchInput}\n              onChange={setSearchInput}\n              invalid={searchInvalid}\n              t={t}\n            />\n            <TimeframeMenu\n              value={timeframe}\n              options={timeframes}\n              onChange={pickTimeframe}\n              range={customRange}\n              onRangeChange={setCustomRange}\n              t={t}\n            />\n            <CategoryMenu\n              selected={categories}\n              onToggle={toggleCategory}\n              onClear={() => setCategories([])}\n              t={t}\n            />\n          </div>\n        </div>\n\n        {isLoading ? (\n          <FeedSkeleton />\n        ) : isError ? (\n          <ErrorState onRetry={() => void refetch()} t={t} />\n        ) : activities.length === 0 ? (\n          // Don't claim nothing matches while more pages remain: the legacy\n          // adapter filters slugs in memory, so a page can come back empty\n          // with more still to load.\n          hasNextPage ? null : isFiltered ? (\n            <NoResultsState onClear={clearFilters} t={t} />\n          ) : (\n            <EmptyState t={t} />\n          )\n        ) : (\n          <div className=\"mt-6\">\n            {groups.map((group, groupIndex) => (\n              <section key={group.key}>\n                <header className=\"text-muted-foreground py-3 pl-11 text-xs font-semibold\">\n                  {group.label}\n                </header>\n                <ul className=\"list-none\">\n                  {group.activities.map((activity, index) => (\n                    <ActivityRow\n                      key={activity.id}\n                      activity={activity}\n                      wasUnread={firstSeenUnread.current.has(activity.id)}\n                      /* Counted across the feed, not the day: the timeline is one\n                         line broken only by a date header. */\n                      spine={spinePositionAt(\n                        (rowOffsets[groupIndex] ?? 0) + index,\n                        totalRows,\n                      )}\n                      index={index}\n                      locale={locale}\n                      t={t}\n                    />\n                  ))}\n                </ul>\n              </section>\n            ))}\n          </div>\n        )}\n\n        {hasNextPage && (\n          <div className=\"mt-3 flex justify-center\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => void fetchNextPage()}\n              disabled={isFetchingNextPage}\n            >\n              {isFetchingNextPage\n                ? t(\"activity_loading\")\n                : t(\"activity_load_more\")}\n            </Button>\n          </div>\n        )}\n      </div>\n    </>\n  );\n}\n\n// ─── row ──────────────────────────────────────────────────────────────────\n\n// Drawn per row so the spine stops at the first and last avatar. -bottom-6\n// rather than bottom-0: the spine resolves against the row's content box,\n// which excludes the pb-6, so bottom-0 left a gap before each next icon. last\n// is a short stub since nothing follows it.\nconst SPINE_CLASS: Record<SpinePosition, string> = {\n  only: \"hidden\",\n  first: \"top-9 -bottom-6\",\n  middle: \"top-0 -bottom-6\",\n  last: \"top-0 h-4\",\n};\n\n// Rows fade in staggered top to bottom; the delay is capped so a full page\n// isn't still animating seconds after it arrives.\nconst ROW_STAGGER_MS = 40;\nconst MAX_STAGGERED_ROWS = 8;\n\n/**\n * Photo, then initials, then the type icon, with the type kept as a corner badge\n * whenever a face takes the circle. The widget does the same, and without it a\n * cart, an order and a page view all look alike once the rows have faces.\n *\n * Keyed on the failed URL rather than a flag: rows survive a refetch, so a photo\n * that 404s once would otherwise never be retried after the contact replaces it.\n * Decorative throughout, since the row already names the actor and the event.\n */\nfunction ActorBadge({\n  avatarUrl,\n  initials,\n  Icon,\n}: {\n  avatarUrl: string | null;\n  initials: string;\n  Icon: LucideIcon;\n}): JSX.Element {\n  const [failedUrl, setFailedUrl] = useState<string | null>(null);\n\n  if (avatarUrl && failedUrl !== avatarUrl) {\n    return (\n      <>\n        <img\n          src={avatarUrl}\n          alt=\"\"\n          width={32}\n          height={32}\n          loading=\"lazy\"\n          onError={() => setFailedUrl(avatarUrl)}\n          className=\"ring-border size-8 rounded-full object-cover ring-1\"\n        />\n        <TypeBadge Icon={Icon} />\n      </>\n    );\n  }\n\n  if (initials) {\n    return (\n      <>\n        <span\n          aria-hidden=\"true\"\n          className=\"ring-border bg-muted text-foreground flex size-8 items-center justify-center rounded-full text-[11px] font-semibold tracking-wide ring-1\"\n        >\n          {initials}\n        </span>\n        <TypeBadge Icon={Icon} />\n      </>\n    );\n  }\n\n  return (\n    <span\n      aria-hidden=\"true\"\n      className=\"ring-border bg-muted text-foreground flex size-8 items-center justify-center rounded-full ring-1\"\n    >\n      <Icon className=\"size-3.5\" />\n    </span>\n  );\n}\n\n/** Sits opposite the unread dot, so the two never cover each other. */\nfunction TypeBadge({ Icon }: { Icon: LucideIcon }): JSX.Element {\n  return (\n    <span\n      aria-hidden=\"true\"\n      className=\"bg-muted text-foreground ring-background absolute -right-0.5 -bottom-0.5 flex size-3.5 items-center justify-center rounded-full ring-2\"\n    >\n      <Icon className=\"size-2\" />\n    </span>\n  );\n}\n\n/**\n * A name only wears the link colour when it really opens: an underline on hover is\n * no affordance on a phone, and colouring a name that goes nowhere is worse than\n * leaving it plain. A visitor stays muted either way, having no page to open.\n */\nfunction actorNameClass(muted: boolean, opensContact: boolean): string {\n  if (muted) return \"text-muted-foreground italic\";\n\n  return opensContact\n    ? \"text-primary font-medium\"\n    : \"text-foreground/80 font-medium\";\n}\n\nfunction ActivityRow({\n  activity,\n  spine,\n  index,\n  locale,\n  t,\n  wasUnread,\n}: {\n  activity: Activity;\n  spine: SpinePosition;\n  index: number;\n  locale: string;\n  t: ShellT;\n  wasUnread: boolean;\n}): JSX.Element {\n  const Icon = getActivityIcon(activity.slug);\n  const isUnread = wasUnread || activity.readAt === null;\n  const actor = describeActor(activity.actor, t);\n  const headline = activity.title ?? activity.activityType;\n  // Only an identified contact has a page to open, and only in a portal that\n  // carries the contacts screen.\n  const contactPath =\n    activity.actor.type === \"contact\" && activity.actor.contactId\n      ? `${CONTACTS_SLUG}/${activity.actor.contactId}`\n      : null;\n  const opensContact = usePortalDetailReachable(contactPath);\n\n  return (\n    <li\n      className=\"animate-in fade-in slide-in-from-bottom-2 relative flex gap-3 pb-6 duration-300 motion-reduce:animate-none\"\n      style={{\n        animationDelay: `${Math.min(index, MAX_STAGGERED_ROWS) * ROW_STAGGER_MS}ms`,\n      }}\n    >\n      <div className=\"relative flex w-8 shrink-0 justify-center\">\n        <span\n          aria-hidden=\"true\"\n          className={`border-primary/40 absolute left-1/2 -translate-x-1/2 border-l-2 border-dashed ${SPINE_CLASS[spine]}`}\n        />\n        {/* Opaque background so the circle knocks a hole in the spine. */}\n        <span className=\"bg-background relative z-10 flex size-8 items-center justify-center rounded-full\">\n          <ActorBadge\n            avatarUrl={activity.avatarUrl}\n            initials={initialsForActor(activity.actor)}\n            Icon={Icon}\n          />\n          {isUnread && (\n            <span\n              className=\"bg-primary border-background absolute -top-0.5 -right-0.5 size-2.5 rounded-full border-2\"\n              aria-label={t(\"activity_unread\")}\n            />\n          )}\n        </span>\n      </div>\n\n      <div className=\"min-w-0 flex-1\">\n        <div className=\"flex items-baseline gap-2\">\n          <p className=\"text-foreground min-w-0 flex-1 truncate text-sm font-semibold\">\n            {headline}\n          </p>\n          <time\n            dateTime={activity.timestamp}\n            className=\"text-muted-foreground shrink-0 text-xs tabular-nums\"\n          >\n            {formatActivityTime(activity.timestamp, locale)}\n          </time>\n        </div>\n\n        <p className=\"text-muted-foreground mt-0.5 truncate text-xs\">\n          <PortalDetailLink to={contactPath} className=\"hover:underline\">\n            <span className={actorNameClass(actor.muted, opensContact)}>\n              {actor.label}\n            </span>\n          </PortalDetailLink>\n          {/* description, not targetName: the server builds targetName as\n              description-or-title for the one-line home widget, so it usually\n              repeats the headline. Some producers copy the headline into\n              description too, so exact repeats are dropped. */}\n          {activity.description && activity.description !== headline && (\n            <>\n              <span className=\"mx-1\">·</span>\n              <span>{activity.description}</span>\n            </>\n          )}\n        </p>\n\n        {activity.details && (\n          <ActivityDetailCard details={activity.details} t={t} />\n        )}\n      </div>\n    </li>\n  );\n}\n\n// ─── filters ──────────────────────────────────────────────────────────────\n\n/**\n * Radix hands radio groups back a bare string. Narrowing through the option\n * list means an unknown value is a no-op instead of an invalid request.\n */\nfunction pickOption<T extends string>(\n  options: ReadonlyArray<{ value: T }>,\n  candidate: string,\n): T | null {\n  return options.find((option) => option.value === candidate)?.value ?? null;\n}\n\nfunction TimeframeMenu({\n  value,\n  options,\n  onChange,\n  range,\n  onRangeChange,\n  t,\n}: {\n  value: ActivityTimeframe;\n  // Element type borrowed from TIMEFRAMES so labelKey stays the narrow literal\n  // union. Widening it to keyof ShellDict pulls in keys that take interpolation,\n  // and then `t` demands an argument these labels do not have.\n  options: ReadonlyArray<(typeof TIMEFRAMES)[number]>;\n  onChange: (next: ActivityTimeframe) => void;\n  range: ActivityDateRange;\n  onRangeChange: (next: ActivityDateRange) => void;\n  t: ShellT;\n}): JSX.Element {\n  const active = options.find((frame) => frame.value === value);\n  const fromId = useId();\n  const toId = useId();\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button variant=\"outline\" size=\"sm\" className=\"gap-1.5\">\n          <Calendar\n            className=\"text-muted-foreground size-3.5\"\n            aria-hidden=\"true\"\n          />\n          {t(active?.labelKey ?? \"activity_timeframe_all\")}\n          <ChevronDown className=\"size-3\" aria-hidden=\"true\" />\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuRadioGroup\n          value={value}\n          onValueChange={(next) => {\n            const picked = pickOption(options, next);\n            if (picked) onChange(picked);\n          }}\n        >\n          {options.map((frame) => (\n            <DropdownMenuRadioItem key={frame.value} value={frame.value}>\n              {t(frame.labelKey)}\n            </DropdownMenuRadioItem>\n          ))}\n        </DropdownMenuRadioGroup>\n\n        {value === \"custom\" &&\n          options.some((frame) => frame.value === \"custom\") && (\n            <>\n              <DropdownMenuSeparator />\n              <div\n                className=\"flex flex-col gap-2 p-2\"\n                // Radix routes single characters in a menu into typeahead, which\n                // moves focus to a matching item mid-edit, and swallows Tab. Escape\n                // still closes, from a capture-phase listener this cannot reach.\n                onKeyDown={(event) => event.stopPropagation()}\n              >\n                <Label className=\"text-xs\" htmlFor={fromId}>\n                  {t(\"activity_range_from\")}\n                </Label>\n                <Input\n                  id={fromId}\n                  type=\"date\"\n                  value={range.from ?? \"\"}\n                  max={range.to ?? undefined}\n                  onChange={(event) =>\n                    onRangeChange(withFrom(range, event.target.value))\n                  }\n                />\n                <Label className=\"text-xs\" htmlFor={toId}>\n                  {t(\"activity_range_to\")}\n                </Label>\n                <Input\n                  id={toId}\n                  type=\"date\"\n                  value={range.to ?? \"\"}\n                  // The server refuses an inverted range, so the picker will not\n                  // offer one.\n                  min={range.from ?? undefined}\n                  onChange={(event) =>\n                    onRangeChange(withTo(range, event.target.value))\n                  }\n                />\n              </div>\n            </>\n          )}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n\nfunction ActorTabs({\n  value,\n  onChange,\n  t,\n}: {\n  value: ActorFilter;\n  onChange: (next: ActorFilter) => void;\n  t: ShellT;\n}): JSX.Element {\n  return (\n    <Tabs\n      value={value}\n      onValueChange={(next) => {\n        const picked = pickOption(ACTOR_FILTERS, next);\n        if (picked) onChange(picked);\n      }}\n    >\n      <TabsList aria-label={t(\"activity_filter_actor\")}>\n        {ACTOR_FILTERS.map((filter) => (\n          <TabsTrigger key={filter.value} value={filter.value}>\n            {t(filter.labelKey)}\n          </TabsTrigger>\n        ))}\n      </TabsList>\n    </Tabs>\n  );\n}\n\nfunction SearchField({\n  value,\n  onChange,\n  invalid,\n  t,\n}: {\n  value: string;\n  onChange: (next: string) => void;\n  invalid: boolean;\n  t: ShellT;\n}): JSX.Element {\n  const hintId = useId();\n\n  return (\n    <div className=\"relative\">\n      <Search\n        aria-hidden=\"true\"\n        className=\"text-muted-foreground pointer-events-none absolute top-1/2 left-2 size-3.5 -translate-y-1/2\"\n      />\n      <Input\n        type=\"search\"\n        value={value}\n        onChange={(event) => onChange(event.target.value)}\n        placeholder={t(\"activity_search_placeholder\")}\n        aria-label={t(\"activity_search_placeholder\")}\n        maxLength={MAX_SEARCH_LENGTH}\n        aria-describedby={invalid ? hintId : undefined}\n        // WebKit draws its own clear control for type=\"search\", which would sit\n        // beside the button below as a second way to do the same thing.\n        className=\"h-8 w-44 pl-7 text-xs [&::-webkit-search-cancel-button]:appearance-none\"\n      />\n      {value && (\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          aria-label={t(\"activity_search_clear\")}\n          onClick={() => onChange(\"\")}\n          className=\"absolute top-1/2 right-0 size-8 -translate-y-1/2 px-0\"\n        >\n          <X className=\"size-3.5\" aria-hidden=\"true\" />\n        </Button>\n      )}\n      {invalid && (\n        <p\n          id={hintId}\n          className=\"text-muted-foreground absolute top-full left-0 mt-1 text-[11px]\"\n        >\n          {t(\"activity_search_too_short\", { count: MIN_SEARCH_LENGTH })}\n        </p>\n      )}\n    </div>\n  );\n}\n\nfunction CategoryMenu({\n  selected,\n  onToggle,\n  onClear,\n  t,\n}: {\n  selected: readonly ActivityCategory[];\n  onToggle: (category: ActivityCategory) => void;\n  onClear: () => void;\n  t: ShellT;\n}): JSX.Element {\n  const label =\n    selected.length === 0\n      ? t(\"activity_filter_all_types\")\n      : t(\"activity_filter_types\");\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          className=\"gap-1.5\"\n          aria-label={label}\n        >\n          <Filter className=\"size-3.5\" aria-hidden=\"true\" />\n          {selected.length > 0 && (\n            <span className=\"text-muted-foreground tabular-nums\">\n              {selected.length}\n            </span>\n          )}\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"w-56\">\n        {ACTIVITY_CATEGORY_KEYS.map((category) => (\n          <DropdownMenuCheckboxItem\n            key={category}\n            checked={selected.includes(category)}\n            onCheckedChange={() => onToggle(category)}\n            // Keeps the menu open so several types can be picked in one visit.\n            onSelect={(event) => event.preventDefault()}\n          >\n            {t(CATEGORY_LABEL_KEYS[category])}\n          </DropdownMenuCheckboxItem>\n        ))}\n        {selected.length > 0 && (\n          <>\n            <DropdownMenuSeparator />\n            {/* An action, not a toggle: a checkbox item here would announce a\n                never-checked checkbox to screen readers. */}\n            <DropdownMenuItem onSelect={onClear}>\n              {t(\"activity_clear_filters\")}\n            </DropdownMenuItem>\n          </>\n        )}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n\n// ─── states ───────────────────────────────────────────────────────────────\n\nfunction FeedSkeleton(): JSX.Element {\n  return (\n    <div className=\"mt-6 space-y-6\" aria-hidden=\"true\">\n      {[0, 1, 2, 3, 4].map((row) => (\n        <div key={row} className=\"flex gap-3\">\n          <Skeleton className=\"size-8 shrink-0 rounded-full\" />\n          <div className=\"flex-1 space-y-2\">\n            <Skeleton className=\"h-3.5 w-1/3\" />\n            <Skeleton className=\"h-3 w-2/3\" />\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nfunction EmptyState({ t }: { t: ShellT }): JSX.Element {\n  return (\n    <div className=\"ring-border mt-6 flex flex-col items-center gap-2 rounded-2xl px-6 py-14 text-center ring-1\">\n      <EmptyActivityIcon\n        className=\"text-muted-foreground size-6\"\n        aria-hidden=\"true\"\n      />\n      <p className=\"text-foreground text-sm font-semibold\">\n        {t(\"activity_empty_title\")}\n      </p>\n      <p className=\"text-muted-foreground max-w-sm text-xs\">\n        {t(\"activity_empty_description\")}\n      </p>\n    </div>\n  );\n}\n\nfunction NoResultsState({\n  onClear,\n  t,\n}: {\n  onClear: () => void;\n  t: ShellT;\n}): JSX.Element {\n  return (\n    <div className=\"ring-border mt-6 flex flex-col items-center gap-2 rounded-2xl px-6 py-14 text-center ring-1\">\n      <Filter className=\"text-muted-foreground size-6\" aria-hidden=\"true\" />\n      <p className=\"text-foreground text-sm font-semibold\">\n        {t(\"activity_no_results_title\")}\n      </p>\n      <p className=\"text-muted-foreground max-w-sm text-xs\">\n        {t(\"activity_no_results_description\")}\n      </p>\n      <Button variant=\"outline\" size=\"sm\" className=\"mt-2\" onClick={onClear}>\n        {t(\"activity_clear_filters\")}\n      </Button>\n    </div>\n  );\n}\n\nfunction ErrorState({\n  onRetry,\n  t,\n}: {\n  onRetry: () => void;\n  t: ShellT;\n}): JSX.Element {\n  return (\n    <div className=\"ring-border mt-6 flex flex-col items-center gap-2 rounded-2xl px-6 py-14 text-center ring-1\">\n      <AlertCircle className=\"text-destructive size-6\" aria-hidden=\"true\" />\n      <p className=\"text-foreground text-sm font-semibold\">\n        {t(\"activity_error_title\")}\n      </p>\n      <Button variant=\"outline\" size=\"sm\" className=\"mt-2\" onClick={onRetry}>\n        {t(\"activity_error_retry\")}\n      </Button>\n    </div>\n  );\n}\n","import type { ComponentProps } from \"react\";\nimport type {\n  BackgroundValue,\n  BorderRadiusOptions,\n  ColorOptions,\n  PaddingOptions,\n} from \"../types\";\nimport type { WidgetPropertySchema } from \"../registries/property-schema-types\";\nimport { PortalActivityFeed } from \"./activity/PortalActivityFeed\";\n\ntype ActivityScreenProps = ComponentProps<\"div\"> & {\n  background?: BackgroundValue;\n  textColor?: ColorOptions;\n  accentColor?: ColorOptions;\n  padding?: PaddingOptions;\n  borderRadius?: BorderRadiusOptions;\n};\n\n/**\n * The rep's own activity feed, and the destination the Recent Activity widget\n * clicks into. Needs no API provider of its own: WidgetsApi is mounted app-wide\n * by FluidProvider.\n */\nexport function ActivityScreen({\n  /* eslint-disable @typescript-eslint/no-unused-vars -- destructured to exclude from divProps spread */\n  background,\n  textColor,\n  accentColor,\n  padding,\n  borderRadius,\n  /* eslint-enable @typescript-eslint/no-unused-vars */\n  ...divProps\n}: ActivityScreenProps): React.JSX.Element {\n  return (\n    <div {...divProps}>\n      <PortalActivityFeed />\n    </div>\n  );\n}\n\nexport const activityScreenPropertySchema: WidgetPropertySchema = {\n  widgetType: \"ActivityScreen\",\n  displayName: \"Activity Screen\",\n  tabsConfig: [{ id: \"styling\", label: \"Styling\" }],\n  fields: [],\n} as const satisfies WidgetPropertySchema;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAOA,MAAa,sBAAsB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;AASD,MAAa,6BACX,IAAI,IAAI,CAAC,YAAY,WAAW,CAAU;;AAG5C,MAAM,sBAAsB,CAAC,GAAG,oBAAoB,CAAC,MAClD,GAAG,MAAM,EAAE,SAAS,EAAE,OACxB;;AAGD,SAAgB,uBACd,MAC8B;AAC9B,MAAK,MAAM,OAAO,oBAChB,KAAI,SAAS,OAAO,KAAK,WAAW,MAAM,IAAI,CAC5C,QAAO;;;;;;;AC3Bb,MAAa,2BAA2B,CAAC,cAAc,aAAa;;;;;;;AAyBpE,SAAgB,sBACd,SAAsC,EAAE,EACqB;CAC7D,MAAM,aAAa,eAAe;CAClC,MAAM,QAAQ,OAAO,OAAO,SAAS,CAAC,GAAG,OAAO,MAAM,CAAC,MAAM,GAAG,KAAA;AAEhE,QAAO,iBAAiB;EACtB,UAAU;GACR,GAAG;GACH;GACA,OAAO,WAAW;GAClB,OAAO,KAAK,IAAI,IAAI;GACpB,OAAO,OAAO,MAAM,IAAI;GACxB,OAAO,QAAQ;GACf,OAAO,MAAM;GAEb,OAAO,YAAY;GACnB,OAAO,aAAa;GACrB;EACD,UAAU,EAAE,WAAW,aAAa;GAGlC,MAAM,OAA4B,EAAE,OAAA,IAA2B;AAC/D,OAAI,OAAO,WAAW,KAAM,MAAK,UAAU,OAAO;AAClD,OAAI,MAAO,MAAK,QAAQ;GAGxB,MAAM,QAAQ,OAAO,OAAO,MAAM;AAClC,OAAI,MAAO,MAAK,QAAQ;AACxB,OAAI,OAAO,QAAQ,KAAM,MAAK,OAAO,OAAO;AAC5C,OAAI,OAAO,MAAM,KAAM,MAAK,KAAK,OAAO;AACxC,OAAI,OAAO,YAAY,KAAM,MAAK,WAAW,OAAO;AACpD,OAAI,OAAO,aAAa,KAAM,MAAK,YAAY,OAAO;AACtD,OAAI,aAAa,KAAM,MAAK,SAAS;AACrC,UAAO,WAAW,gBAAgB,MAAM,OAAO;;EAEjD,mBAAmB,aAAa,SAAS,cAAc,KAAA;EACvD,kBAAkB,KAAA;EACnB,CAAC;;;;;;;;;;ACtEJ,SAAS,mBACP,aACM;CACN,MAAM,0BAAS,IAAI,MAAM,EAAC,aAAa;AAEvC,aAAY,eACV,EAAE,UAAU,0BAA0B,GACrC,WACC,UAAU;EACR,GAAG;EACH,OAAO,OAAO,MAAM,KAAK,UAAU;GACjC,GAAG;GAGH,GAAI,KAAK,gBAAgB,KAAA,KAAa,EAAE,aAAa,GAAG;GACxD,YAAY,KAAK,WAAW,KAAK,aAC/B,SAAS,WAAW,OAAO;IAAE,GAAG;IAAU;IAAQ,GAAG,SACtD;GACF,EAAE;EACJ,CACJ;;;;;;;;AASH,SAAgB,4BACd,YACA,aACM;CACN,MAAM,aAAa,eAAe;CAClC,MAAM,cAAc,gBAAgB;CAEpC,MAAM,EAAE,WAAW,YAAY;EAC7B,kBAAkB,WAAW,oBAAoB;EACjD,YAAY,WAAW;AAGrB,OAAI,OAAO,YAAY,eAAgB;AAEvC,sBAAmB,YAAY;;EAIjC,UAAU,UAAU;AAClB,WAAQ,MAAM,kDAAkD,MAAM;;EAEzE,CAAC;CAKF,MAAM,mBAAmB,gBAAgB;CAEzC,MAAM,YAAY,OAAO,MAAM;AAC/B,iBAAgB;AACd,MAAI,CAAC,cAAc,oBAAoB,UAAU,QAAS;AAC1D,YAAU,UAAU;AACpB,UAAQ;IACP;EAAC;EAAY;EAAkB;EAAO,CAAC;;;;AC9D5C,MAAa,aAAa;CACxB;EAAE,OAAO;EAAO,UAAU;EAA0B;CACpD;EAAE,OAAO;EAAS,UAAU;EAA4B;CACxD;EAAE,OAAO;EAAa,UAAU;EAAgC;CAChE;EAAE,OAAO;EAAS,UAAU;EAAwB;CACpD;EAAE,OAAO;EAAU,UAAU;EAA6B;CAC3D;;;;;;AAUD,MAAM,wBAA0D,CAC9D,OACA,QACD;;;;;;AAOD,SAAgB,cACd,eAC4C;AAC5C,KAAI,cAAe,QAAO;AAE1B,QAAO,WAAW,QAAQ,UACxB,sBAAsB,SAAS,MAAM,MAAM,CAC5C;;;;;;;AA8BH,MAAM,kBAAkB;;;;;;AAOxB,SAAgB,SAAS,IAAkB;AAKzC,QAAO,GAJM,GAAG,aAAa,CAId,GAHD,OAAO,GAAG,UAAU,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI,CAGhC,GAFZ,OAAO,GAAG,SAAS,CAAC,CAAC,SAAS,GAAG,IAAI;;;;;;;;;AAYnD,SAAgB,iBAAqC;AACnD,QAAO,KAAK,gBAAgB,CAAC,iBAAiB,CAAC,YAAY,KAAA;;;;;;AAO7D,SAAgB,kBAAkB,MAAsB;CACtD,MAAM,KAAK,IAAI,KAAK,KAAK;AAEzB,QAAO,IAAI,KACT,GAAG,aAAa,EAChB,GAAG,UAAU,EACb,GAAG,SAAS,GAAG,GACf,GACA,GACA,GACA,EACD,CAAC,SAAS;;;;;;;;;AAUb,SAAgB,iBACd,WACA,OACA,SAA4B,EAAE,EACd;CAChB,MAAM,QAAQ,OAAU,KAAK;CAC7B,MAAM,QAAQ,SAAS,IAAI,KAAK,MAAM,CAAC;AAEvC,SAAQ,WAAR;EACE,KAAK,QACH,QAAO;GAAE,MAAM;GAAO,IAAI;GAAO;EACnC,KAAK,aAAa;GAChB,MAAM,YAAY,SAAS,IAAI,KAAK,QAAQ,MAAM,CAAC;AACnD,UAAO;IAAE,MAAM;IAAW,IAAI;IAAW;;EAE3C,KAAK,QACH,QAAO,EAAE,SAAS,iBAAiB;EACrC,KAAK;AAKH,OAAI,OAAO,QAAQ,OAAO,MAAM,OAAO,KAAK,OAAO,KACjD,QAAO;IAAE,MAAM,OAAO;IAAM,IAAI,OAAO;IAAM;AAG/C,UAAO;IACL,GAAI,OAAO,OAAO,EAAE,MAAM,OAAO,MAAM,GAAG,EAAE;IAC5C,GAAI,OAAO,KAAK,EAAE,IAAI,OAAO,IAAI,GAAG,EAAE;IACvC;EAEH,KAAK,MACH,QAAO,EAAE;EACX,QACE,QAAO,2BAA2B,UAAU;;;;AAKlD,SAAS,2BAA2B,WAAkC;AAGpE,QAAO,EAAE;;;;;;;AAQX,SAAgB,aACd,OACA,eACA,gBACS;AACT,KAAI,MAAM,SAAS,eAAgB,QAAO;CAE1C,MAAM,QAAQ,MAAM,MAAM,MAAM,CAAC,OAAO,QAAQ;AAEhD,QACE,MAAM,SAAS,KAAK,MAAM,OAAO,SAAS,KAAK,UAAU,cAAc;;;;;;;;;AAkB3E,SAAgB,qBACd,YACA,OACA,QACA,QACoB;CACpB,MAAM,SAA6B,EAAE;CACrC,MAAM,wBAAQ,IAAI,KAA+B;AAEjD,MAAK,MAAM,YAAY,YAAY;EACjC,MAAM,MAAM,mBAAmB,SAAS,UAAU;EAClD,MAAM,WAAW,MAAM,IAAI,IAAI;AAC/B,MAAI,UAAU;AACZ,YAAS,WAAW,KAAK,SAAS;AAClC;;EAGF,MAAM,QAA0B;GAC9B;GACA,OAAO,yBACL,SAAS,WACT,OACA,QACA,OACD;GACD,YAAY,CAAC,SAAS;GACvB;AACD,QAAM,IAAI,KAAK,MAAM;AACrB,SAAO,KAAK,MAAM;;AAGpB,QAAO;;;;;;;AAeT,SAAgB,gBAAgB,OAAe,OAA8B;AAC3E,KAAI,SAAS,EAAG,QAAO;AACvB,KAAI,UAAU,EAAG,QAAO;AACxB,KAAI,UAAU,QAAQ,EAAG,QAAO;AAChC,QAAO;;;;;;AAOT,SAAgB,kBACd,QACU;CACV,IAAI,UAAU;AAEd,QAAO,OAAO,KAAK,UAAU;EAC3B,MAAM,SAAS;AACf,aAAW,MAAM,WAAW;AAC5B,SAAO;GACP;;;;;;;;AAWJ,SAAgB,SACd,OACA,MACmB;CACnB,MAAM,KAAK,MAAM,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,MAAM;AAE9D,QAAO;EAAE,GAAI,QAAQ,EAAE,MAAM;EAAG,GAAI,MAAM,EAAE,IAAI;EAAG;;AAGrD,SAAgB,OACd,OACA,IACmB;CACnB,MAAM,OAAO,MAAM,QAAQ,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM;AAE9D,QAAO;EAAE,GAAI,QAAQ,EAAE,MAAM;EAAG,GAAI,MAAM,EAAE,IAAI;EAAG;;;;;;;;;ACxRrD,MAAa,UAET,WAA4C,SAAS,QACvD,EAAE,IAAI,SAAS,UAAU,GAAG,QAC5B,KACA;CACA,MAAM,EAAE,UAAU,cAAc,kBAAkB;CAElD,MAAM,eAAe,MAAqC;AAExD,YAAU,EAAE;AACZ,MAAI,EAAE,iBAAkB;AAGxB,MAAI,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,OAAQ;AACtD,MAAI,EAAE,WAAW,EAAG;AACpB,MAAI,KAAK,UAAU,KAAK,WAAW,QAAS;AAE5C,IAAE,gBAAgB;AAClB,WAAS,GAAG;;AAGd,QACE,oBAAC,KAAD;EAAQ;EAAK,MAAM,UAAU,GAAG;EAAE,SAAS;EAAa,GAAI;EACzD;EACC,CAAA;EAEN;;;;;;;;;;AC7BF,MAAM,oBAA4C;CAChD,SAAS;CACT,QAAQ;CACR,SAAS;CACV;;AAGD,MAAM,iBAAiB;;AAGvB,MAAa,gBAAgB;AAE7B,SAAS,cACP,YACA,UACe;AACf,KAAI,CAAC,cAAc,CAAC,SAAU,QAAO;CAErC,MAAM,SAAS,kBAAkB;AAEjC,QAAO,SAAS,GAAG,OAAO,GAAG,aAAa;;;;;;;;;AAU5C,SAAgB,iBAAiB,SAAyC;AACxE,SAAQ,QAAQ,MAAhB;EAEE,KAAK,OACH,QAAO,cAAc,WAAW,QAAQ,UAAU;EACpD,KAAK,QACH,QAAO,cAAc,UAAU,QAAQ,SAAS;EAMlD,KAAK,UACH,QAAO,QAAQ,kBAAkB,QAAQ,YACrC,4BAA4B,gBAAgB;GAC1C,MAAM;GACN,gBAAgB,QAAQ;GACxB,WAAW,OAAO,QAAQ,UAAU;GACrC,CAAC,GACF;EAEN,KAAK,WACH,QAAO,cAAc,QAAQ,cAAc,QAAQ,WAAW;EAChE,KAAK,SACH,QAAO,cAAc,QAAQ,gBAAgB,QAAQ,aAAa;EACpE,KAAK,WACH,QAAO,cAAc,QAAQ,eAAe,QAAQ,YAAY;EAClE,KAAK,UACH,QAAO,cAAc,QAAQ,gBAAgB,QAAQ,aAAa;EAEpE,QACE,QAAO;;;AAIb,SAAS,SAAS,OAA4C;AAC5D,QAAO,MAAM,SAAS,SAAS,CAC7B,GAAI,KAAK,OAAO,CAAC,KAAK,KAAK,GAAG,EAAE,EAChC,GAAG,SAAS,KAAK,YAAY,EAAE,CAAC,CACjC,CAAC;;;;;;;;;AAUJ,SAAS,YAAY,MAAc,OAA2C;CAC5E,MAAM,OAAO,uBAAuB,KAAK;AACzC,KAAI,CAAC,KAAM,QAAO;AAClB,KAAI,CAAC,2BAA2B,IAAI,KAAK,CAAE,QAAO;AAElD,QAAO,SAAS,MAAM,CAAC,MACpB,YAAY,uBAAuB,QAAQ,KAAK,KAClD;;;;;;;;;AAUH,SAAgB,yBAAyB,IAA4B;CACnE,MAAM,aAAa,0BAA0B;AAE7C,KAAI,CAAC,MAAM,CAAC,WAAY,QAAO;AAE/B,QAAO,YAAY,IAAI,WAAW,SAAS;;;;;;;AAQ7C,SAAgB,iBAAiB,EAC/B,IACA,WACA,YAKc;CACd,MAAM,YAAY,yBAAyB,GAAG;AAE9C,KAAI,CAAC,MAAM,CAAC,UAAW,QAAO,oBAAA,YAAA,EAAG,UAAY,CAAA;AAE7C,QACE,oBAAC,SAAD;EAAa;EAAe;EACzB;EACO,CAAA;;;;;;;;;;;AC5Hd,SAAgB,mBAAmB,EACjC,SACA,KAC8C;AAC9C,SAAQ,QAAQ,MAAhB;EACE,KAAK,SAAS;GACZ,MAAM,QAAQ,QAAQ,wBAAwB,QAAQ;AACtD,OAAI,QAAQ,iBAAiB,QAAQ,UAAU;IAC7C,MAAM,cAAc,QAAQ,kBAAkB,KAAK;IACnD,MAAM,WAAW,CACf,OACA,aAAa,IACT,EAAE,6BAA6B,EAAE,OAAO,YAAY,CAAC,GACrD,KACL,CACE,OAAO,QAAQ,CACf,KAAK,MAAM;AACd,WACE,oBAAC,WAAD;KACE,OAAO,QAAQ;KACf,UAAU,QAAQ;KAClB,UAAU,YAAY,KAAA;KACtB,CAAA;;AAGN,UAAO,QACL,oBAAC,UAAD;IAAU,OAAO,EAAE,uBAAuB;IAAE,OAAO;IAAS,CAAA,GAC1D;;EAGN,KAAK,QACH,QAAO,QAAQ,eAAe,QAAQ,WACpC,oBAAC,kBAAD;GAAkB,IAAI,iBAAiB,QAAQ;GAAE,WAAU;aACzD,oBAAC,WAAD;IAAW,OAAO,QAAQ;IAAa,UAAU,QAAQ;IAAY,CAAA;GACpD,CAAA,GACjB;EAEN,KAAK,OACH,QAAO,QAAQ,eAAe,QAAQ,WACpC,oBAAC,kBAAD;GAAkB,IAAI,iBAAiB,QAAQ;GAAE,WAAU;aACzD,oBAAC,WAAD;IAAW,OAAO,QAAQ;IAAa,UAAU,QAAQ;IAAY,CAAA;GACpD,CAAA,GACjB;EAIN,KAAK,YAAY;GACf,MAAM,KAAK,iBAAiB,QAAQ;GACpC,MAAM,QAAQ,QAAQ,gBAAgB,QAAQ;AAE9C,OAAI,MAAM,MACR,QACE,oBAAC,kBAAD;IAAsB;IAAI,WAAW;cAClC;IACgB,CAAA;AAIvB,UAAO,QAAQ,MACb,oBAAC,UAAD;IAAU,OAAO,SAAS,QAAQ;IAAK,KAAK,QAAQ;IAAO,CAAA,GACzD;;EAKN,KAAK,cACH,QAAO;EAET,KAAK,UACH,QAAO,QAAQ,OACb,oBAAC,kBAAD;GAAkB,IAAI,iBAAiB,QAAQ;GAAE,WAAU;aACzD,oBAAC,WAAD;IAAW,MAAM,QAAQ;IAAM,SAAS,QAAQ;IAAkB,CAAA;GACjD,CAAA,GACjB;EAEN,KAAK,SACH,QAAO,QAAQ,UAAU,QAAQ,QAAQ,OACvC,oBAAC,kBAAD;GAAkB,IAAI,iBAAiB,QAAQ;GAAE,WAAU;aACzD,oBAAC,YAAD;IACE,QAAQ,QAAQ;IAChB,MAAM,QAAQ;IACd,SAAS,QAAQ;IACd;IACH,CAAA;GACe,CAAA,GACjB;EAEN,KAAK,WACH,QAAO,QAAQ,OACb,oBAAC,kBAAD;GAAkB,IAAI,iBAAiB,QAAQ;GAAE,WAAU;aACzD,oBAAC,cAAD;IAAc,MAAM,QAAQ;IAAM,SAAS,QAAQ;IAAkB,CAAA;GACpD,CAAA,GACjB;EAIN,KAAK,UACH,QAAO,QAAQ,QAAQ,QAAQ,UAC7B,oBAAC,kBAAD;GAAkB,IAAI,iBAAiB,QAAQ;GAAE,WAAU;aACzD,oBAAC,WAAD;IACE,SAAS,QAAQ,OAAO,QAAQ,UAAU,KAAA;IAC1C,MAAM,QAAQ,QAAQ,QAAQ,WAAW;IACzC,CAAA;GACe,CAAA,GACjB;EAEN,QACE,QAAO;;;AAIb,MAAM,aACJ;AAEF,SAAS,UAAU,EACjB,OACA,UACA,YAKc;AACd,QACE,qBAAC,OAAD;EAAK,WAAW;YAAhB,CACE,oBAAC,WAAD;GAAW,KAAK;GAAU,KAAK,SAAS;GAAM,CAAA,EAC9C,qBAAC,OAAD;GAAK,WAAU;aAAf,CACG,SACC,oBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA,EAEL,YACC,oBAAC,KAAD;IAAG,WAAU;cAA0C;IAAa,CAAA,CAElE;KACF;;;AAIV,SAAS,UAAU,EACjB,KACA,OAIc;CACd,MAAM,CAAC,QAAQ,aAAa,SAAS,MAAM;AAE3C,KAAI,CAAC,OAAO,OACV,QACE,oBAAC,OAAD;EACE,eAAY;EACZ,WAAU;YAEV,oBAAC,UAAD,EAAU,WAAU,UAAW,CAAA;EAC3B,CAAA;AAIV,QACE,oBAAC,OAAD;EACO;EACA;EACL,SAAQ;EACR,eAAe,UAAU,KAAK;EAC9B,WAAU;EACV,CAAA;;AAIN,SAAS,SAAS,EAChB,OACA,SAIc;AACd,QACE,oBAAC,OAAD;EAAK,WAAW;YACd,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,KAAD;IAAG,WAAU;cAA6C;IAAU,CAAA,EACpE,oBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA,CACA;;EACF,CAAA;;AAIV,MAAM,aAAa;AAEnB,SAAS,SAAS,EAAE,OAAO,OAAoD;AAC7E,QACE,oBAAC,KAAD;EACE,MAAM;EACN,QAAO;EACP,KAAI;EACJ,WAAW;YAEV;EACC,CAAA;;AAIR,SAAS,UAAU,EACjB,SACA,MACA,WAKc;AACd,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,WAAW;YAA9B;GACG,WACC,oBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA;GAIN,oBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA;GACH,WAAW,oBAAC,KAAD;IAAG,WAAU;cAAiC;IAAY,CAAA;GAClE;;;AAIV,SAAS,WAAW,EAClB,QACA,MACA,SACA,KAMc;AACd,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,WAAW;YAA9B;GACG,UAAU,QACT,oBAAC,OAAD;IACE,WAAU;IACV,cAAY,EAAE,mBAAmB,EAAE,QAAQ,CAAC;cAE3C;KAAC;KAAG;KAAG;KAAG;KAAG;KAAE,CAAC,KAAK,SACpB,oBAAC,MAAD;KAEE,eAAY;KACZ,WACE,QAAQ,SACJ,uCACA;KAEN,EAPK,KAOL,CACF;IACE,CAAA;GAEP,QACC,oBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA;GAEL,WAAW,oBAAC,KAAD;IAAG,WAAU;cAAiC;IAAY,CAAA;GAClE;;;AAIV,SAAS,aAAa,EACpB,MACA,WAIc;AACd,QACE,qBAAC,OAAD;EAAK,WAAW;YAAhB,CACE,oBAAC,QAAD;GAAM,WAAU;GAAuB,eAAY;aAChD;GACI,CAAA,EACN,WACC,oBAAC,KAAD;GAAG,WAAU;aACV;GACC,CAAA,CAEF;;;;;;;;;AChOV,MAAM,oBAAuC;AAK7C,MAAM,gBAAgB;CACpB;EAAE,OAAO;EAAO,UAAU;EAA6B;CACvD;EAAE,OAAO;EAAW,UAAU;EAAkC;CAChE;EAAE,OAAO;EAAW,UAAU;EAAkC;CACjE;AAKD,MAAM,sBAAsB;CAC1B,QAAQ;CACR,OAAO;CACP,UAAU;CACV,QAAQ;CACR,WAAW;CACX,OAAO;CACP,SAAS;CACT,WAAW;CACZ;AAED,SAAgB,qBAAkC;CAChD,MAAM,EAAE,GAAG,WAAW,qBAAqB;CAI3C,MAAM,EAAE,WAAW,eAAe,WAAW,kBAC3C,eAAe,CAAC;CAClB,MAAM,CAAC,WAAW,gBAChB,SAA4B,kBAAkB;CAChD,MAAM,CAAC,aAAa,kBAAkB,SAA4B,EAAE,CAAC;CACrE,MAAM,CAAC,YAAY,iBAAiB,SAA6B,EAAE,CAAC;CACpE,MAAM,CAAC,aAAa,kBAAkB,SAAsB,MAAM;CAIlE,MAAM,CAAC,aAAa,kBAAkB,SAAS,GAAG;CAClD,MAAM,mBAAmB,6BAA6B;CAKtD,MAAM,kBAAkB,uBAAoB,IAAI,KAAK,CAAC;CACtD,MAAM,CAAC,YAAY,iBAAiB,SAAS,GAAG;CAEhD,MAAM,QAAQ,cAAc,mBAAmB,WAAW,EAAE,CAAC,WAAW,CAAC;CACzE,MAAM,aAAa,cACX,cAAc,cAAc,EAClC,CAAC,cAAc,CAChB;CAMD,MAAM,CAAC,OAAO,YAAY,eAAe,KAAK,KAAK,CAAC;AAEpD,iBAAgB;EACd,MAAM,WAAW,kBAAkB,MAAM;EACzC,MAAM,QAAQ,iBACN,SAAS,SAAS,EACxB,KAAK,IAAI,WAAW,KAAK,KAAK,EAAE,EAAE,CACnC;AAED,eAAa,aAAa,MAAM;IAC/B,CAAC,MAAM,CAAC;CAEX,MAAM,QAAQ,cACN,iBAAiB,WAAW,OAAO,YAAY,EACrD;EAAC;EAAW;EAAO;EAAY,CAChC;CAID,MAAM,CAAC,YAAY,SAAS,eAAe;CAE3C,MAAM,eAAe,YAAY,MAAM;CACvC,MAAM,cAAc,aAClB,cAAA,GAAA,IAGD;CACD,MAAM,gBAAgB,aAAa,SAAS,KAAK,CAAC;AAKlD,iBAAgB;AACd,MAAI,cAAe;EAEnB,MAAM,OAAO,cAAc,eAAe;AAC1C,MAAI,SAAS,WAAY;EAEzB,MAAM,QAAQ,iBAAiB,cAAc,KAAK,EAAA,IAAqB;AACvE,eAAa,aAAa,MAAM;IAC/B;EAAC;EAAc;EAAa;EAAe;EAAW,CAAC;CAE1D,MAAM,EACJ,MACA,WACA,SACA,WACA,SACA,eACA,aACA,uBACE,sBAAsB;EACxB,GAAI,MAAM,SAAS,KAAK,EAAE,OAAO;EACjC,GAAI,cAAc,EAAE,OAAO,YAAY;EACvC,GAAI,MAAM,WAAW,QAAQ,EAAE,SAAS,MAAM,SAAS;EACvD,GAAI,MAAM,QAAQ,EAAE,MAAM,MAAM,MAAM;EACtC,GAAI,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI;EAGhC,IAAK,MAAM,QAAQ,MAAM,OAAO,WAAW,EAAE,UAAU,GAAG,EAAE;EAC5D,GAAI,gBAAgB,SAAS,EAAE,WAAW,aAAa;EACxD,CAAC;AAIF,6BAA4B,WAAW,MAAM,MAAM,IAAI,YAAY;CAEnE,MAAM,aAAa,cACX,MAAM,MAAM,SAAS,SAAS,KAAK,WAAW,IAAI,EAAE,EAC1D,CAAC,KAAK,CACP;CAED,MAAM,aAAa,MAAM,MAAM,IAAI;AAInC,MAAK,MAAM,YAAY,WACrB,KAAI,SAAS,WAAW,KAAM,iBAAgB,QAAQ,IAAI,SAAS,GAAG;CAGxE,MAAM,YAAY,eACT;EACL,OAAO,EAAE,sBAAsB;EAC/B,WAAW,EAAE,0BAA0B;EACxC,GACD,CAAC,EAAE,CACJ;CACD,MAAM,SAAS,cACP,qBAAqB,YAAY,OAAO,WAAW,OAAO,EAChE;EAAC;EAAY;EAAO;EAAW;EAAO,CACvC;CACD,MAAM,aAAa,cAAc,kBAAkB,OAAO,EAAE,CAAC,OAAO,CAAC;CACrE,MAAM,YAAY,WAAW;CAK7B,MAAM,aACJ,WAAW,SAAS,KACpB,MAAM,QAAQ,QACd,MAAM,MAAM,QACZ,gBAAgB,SAChB,WAAW,SAAS;CAItB,MAAM,iBAAiB,SAA4B;AACjD,MAAI,SAAS,YAAY,CAAC,YAAY,QAAQ,CAAC,YAAY,IAAI;GAC7D,MAAM,QAAQ,SAAS,IAAI,KAAK,MAAM,CAAC;AACvC,kBAAe;IAAE,MAAM;IAAO,IAAI;IAAO,CAAC;;AAE5C,eAAa,KAAK;;CAGpB,MAAM,qBAAqB;AACzB,gBAAc,EAAE,CAAC;AACjB,eAAa,kBAAkB;AAC/B,iBAAe,EAAE,CAAC;AAClB,iBAAe,MAAM;AACrB,iBAAe,GAAG;AAClB,gBAAc,GAAG;;CAGnB,MAAM,kBAAkB,aAA+B;AACrD,iBAAe,YACb,QAAQ,SAAS,SAAS,GACtB,QAAQ,QAAQ,UAAU,UAAU,SAAS,GAC7C,CAAC,GAAG,SAAS,SAAS,CAC3B;;AAGH,QACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,yBAAD,EAAA,UACE,oBAAC,YAAD,EAAA,UACE,qBAAC,gBAAD;EAAgB,WAAU;YAA1B,CAGG,oBACC,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,gBAAD,EAAA,UACE,oBAAC,gBAAD;GACE,MAAK;GACL,UAAU,UAAU;AAClB,UAAM,gBAAgB;AACtB,qBAAiB,SAAS;;aAG3B,iBAAiB;GACH,CAAA,EACF,CAAA,EACjB,oBAAC,qBAAD,EAAuB,CAAA,CACtB,EAAA,CAAA,EAEL,oBAAC,gBAAD,EAAA,UACE,oBAAC,gBAAD;GAAgB,WAAU;aACvB,EAAE,iBAAiB;GACL,CAAA,EACF,CAAA,CACF;KACN,CAAA,EACW,CAAA,EAE1B,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,MAAD;IAAI,WAAU;cACX,EAAE,iBAAiB;IACjB,CAAA,EAEJ,cAAc,QACb,oBAAC,KAAD;IAAG,WAAU;cACV,EAAE,kBAAkB,EAAE,OAAO,YAAY,CAAC;IACzC,CAAA,CAEF,EAAA,CAAA;GAEN,qBAAC,OAAD;IAAK,WAAU;cAAf,CACG,iBACC,oBAAC,WAAD;KAAW,OAAO;KAAa,UAAU;KAAmB;KAAK,CAAA,EAEnE,qBAAC,OAAD;KAAK,WAAU;eAAf;MACE,oBAAC,aAAD;OACE,OAAO;OACP,UAAU;OACV,SAAS;OACN;OACH,CAAA;MACF,oBAAC,eAAD;OACE,OAAO;OACP,SAAS;OACT,UAAU;OACV,OAAO;OACP,eAAe;OACZ;OACH,CAAA;MACF,oBAAC,cAAD;OACE,UAAU;OACV,UAAU;OACV,eAAe,cAAc,EAAE,CAAC;OAC7B;OACH,CAAA;MACE;OACF;;GAEL,YACC,oBAAC,cAAD,EAAgB,CAAA,GACd,UACF,oBAAC,YAAD;IAAY,eAAe,KAAK,SAAS;IAAK;IAAK,CAAA,GACjD,WAAW,WAAW,IAIxB,cAAc,OAAO,aACnB,oBAAC,gBAAD;IAAgB,SAAS;IAAiB;IAAK,CAAA,GAE/C,oBAAC,YAAD,EAAe,GAAK,CAAA,GAGtB,oBAAC,OAAD;IAAK,WAAU;cACZ,OAAO,KAAK,OAAO,eAClB,qBAAC,WAAD,EAAA,UAAA,CACE,oBAAC,UAAD;KAAQ,WAAU;eACf,MAAM;KACA,CAAA,EACT,oBAAC,MAAD;KAAI,WAAU;eACX,MAAM,WAAW,KAAK,UAAU,UAC/B,oBAAC,aAAD;MAEY;MACV,WAAW,gBAAgB,QAAQ,IAAI,SAAS,GAAG;MAGnD,OAAO,iBACJ,WAAW,eAAe,KAAK,OAChC,UACD;MACM;MACC;MACL;MACH,EAZK,SAAS,GAYd,CACF;KACC,CAAA,CACG,EAAA,EAtBI,MAAM,IAsBV,CACV;IACE,CAAA;GAGP,eACC,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,QAAD;KACE,SAAQ;KACR,MAAK;KACL,eAAe,KAAK,eAAe;KACnC,UAAU;eAET,qBACG,EAAE,mBAAmB,GACrB,EAAE,qBAAqB;KACpB,CAAA;IACL,CAAA;GAEJ;IACL,EAAA,CAAA;;AAUP,MAAM,cAA6C;CACjD,MAAM;CACN,OAAO;CACP,QAAQ;CACR,MAAM;CACP;AAID,MAAM,iBAAiB;AACvB,MAAM,qBAAqB;;;;;;;;;;AAW3B,SAAS,WAAW,EAClB,WACA,UACA,QAKc;CACd,MAAM,CAAC,WAAW,gBAAgB,SAAwB,KAAK;AAE/D,KAAI,aAAa,cAAc,UAC7B,QACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,OAAD;EACE,KAAK;EACL,KAAI;EACJ,OAAO;EACP,QAAQ;EACR,SAAQ;EACR,eAAe,aAAa,UAAU;EACtC,WAAU;EACV,CAAA,EACF,oBAAC,WAAD,EAAiB,MAAQ,CAAA,CACxB,EAAA,CAAA;AAIP,KAAI,SACF,QACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,QAAD;EACE,eAAY;EACZ,WAAU;YAET;EACI,CAAA,EACP,oBAAC,WAAD,EAAiB,MAAQ,CAAA,CACxB,EAAA,CAAA;AAIP,QACE,oBAAC,QAAD;EACE,eAAY;EACZ,WAAU;YAEV,oBAAC,MAAD,EAAM,WAAU,YAAa,CAAA;EACxB,CAAA;;;AAKX,SAAS,UAAU,EAAE,QAA2C;AAC9D,QACE,oBAAC,QAAD;EACE,eAAY;EACZ,WAAU;YAEV,oBAAC,MAAD,EAAM,WAAU,UAAW,CAAA;EACtB,CAAA;;;;;;;AASX,SAAS,eAAe,OAAgB,cAA+B;AACrE,KAAI,MAAO,QAAO;AAElB,QAAO,eACH,6BACA;;AAGN,SAAS,YAAY,EACnB,UACA,OACA,OACA,QACA,GACA,aAQc;CACd,MAAM,OAAO,gBAAgB,SAAS,KAAK;CAC3C,MAAM,WAAW,aAAa,SAAS,WAAW;CAClD,MAAM,QAAQ,cAAc,SAAS,OAAO,EAAE;CAC9C,MAAM,WAAW,SAAS,SAAS,SAAS;CAG5C,MAAM,cACJ,SAAS,MAAM,SAAS,aAAa,SAAS,MAAM,YAChD,GAAG,cAAc,GAAG,SAAS,MAAM,cACnC;CACN,MAAM,eAAe,yBAAyB,YAAY;AAE1D,QACE,qBAAC,MAAD;EACE,WAAU;EACV,OAAO,EACL,gBAAgB,GAAG,KAAK,IAAI,OAAO,mBAAmB,GAAG,eAAe,KACzE;YAJH,CAME,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,QAAD;IACE,eAAY;IACZ,WAAW,iFAAiF,YAAY;IACxG,CAAA,EAEF,qBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,oBAAC,YAAD;KACE,WAAW,SAAS;KACpB,UAAU,iBAAiB,SAAS,MAAM;KACpC;KACN,CAAA,EACD,YACC,oBAAC,QAAD;KACE,WAAU;KACV,cAAY,EAAE,kBAAkB;KAChC,CAAA,CAEC;MACH;MAEN,qBAAC,OAAD;GAAK,WAAU;aAAf;IACE,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,KAAD;MAAG,WAAU;gBACV;MACC,CAAA,EACJ,oBAAC,QAAD;MACE,UAAU,SAAS;MACnB,WAAU;gBAET,mBAAmB,SAAS,WAAW,OAAO;MAC1C,CAAA,CACH;;IAEN,qBAAC,KAAD;KAAG,WAAU;eAAb,CACE,oBAAC,kBAAD;MAAkB,IAAI;MAAa,WAAU;gBAC3C,oBAAC,QAAD;OAAM,WAAW,eAAe,MAAM,OAAO,aAAa;iBACvD,MAAM;OACF,CAAA;MACU,CAAA,EAKlB,SAAS,eAAe,SAAS,gBAAgB,YAChD,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,QAAD;MAAM,WAAU;gBAAO;MAAQ,CAAA,EAC/B,oBAAC,QAAD,EAAA,UAAO,SAAS,aAAmB,CAAA,CAClC,EAAA,CAAA,CAEH;;IAEH,SAAS,WACR,oBAAC,oBAAD;KAAoB,SAAS,SAAS;KAAY;KAAK,CAAA;IAErD;KACH;;;;;;;AAUT,SAAS,WACP,SACA,WACU;AACV,QAAO,QAAQ,MAAM,WAAW,OAAO,UAAU,UAAU,EAAE,SAAS;;AAGxE,SAAS,cAAc,EACrB,OACA,SACA,UACA,OACA,eACA,KAWc;CACd,MAAM,SAAS,QAAQ,MAAM,UAAU,MAAM,UAAU,MAAM;CAC7D,MAAM,SAAS,OAAO;CACtB,MAAM,OAAO,OAAO;AAEpB,QACE,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,qBAAD;EAAqB,SAAA;YACnB,qBAAC,QAAD;GAAQ,SAAQ;GAAU,MAAK;GAAK,WAAU;aAA9C;IACE,oBAAC,UAAD;KACE,WAAU;KACV,eAAY;KACZ,CAAA;IACD,EAAE,QAAQ,YAAY,yBAAyB;IAChD,oBAAC,aAAD;KAAa,WAAU;KAAS,eAAY;KAAS,CAAA;IAC9C;;EACW,CAAA,EACtB,qBAAC,qBAAD;EAAqB,OAAM;YAA3B,CACE,oBAAC,wBAAD;GACS;GACP,gBAAgB,SAAS;IACvB,MAAM,SAAS,WAAW,SAAS,KAAK;AACxC,QAAI,OAAQ,UAAS,OAAO;;aAG7B,QAAQ,KAAK,UACZ,oBAAC,uBAAD;IAAyC,OAAO,MAAM;cACnD,EAAE,MAAM,SAAS;IACI,EAFI,MAAM,MAEV,CACxB;GACqB,CAAA,EAExB,UAAU,YACT,QAAQ,MAAM,UAAU,MAAM,UAAU,SAAS,IAC/C,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,uBAAD,EAAyB,CAAA,EACzB,qBAAC,OAAD;GACE,WAAU;GAIV,YAAY,UAAU,MAAM,iBAAiB;aAL/C;IAOE,oBAAC,OAAD;KAAO,WAAU;KAAU,SAAS;eACjC,EAAE,sBAAsB;KACnB,CAAA;IACR,oBAAC,OAAD;KACE,IAAI;KACJ,MAAK;KACL,OAAO,MAAM,QAAQ;KACrB,KAAK,MAAM,MAAM,KAAA;KACjB,WAAW,UACT,cAAc,SAAS,OAAO,MAAM,OAAO,MAAM,CAAC;KAEpD,CAAA;IACF,oBAAC,OAAD;KAAO,WAAU;KAAU,SAAS;eACjC,EAAE,oBAAoB;KACjB,CAAA;IACR,oBAAC,OAAD;KACE,IAAI;KACJ,MAAK;KACL,OAAO,MAAM,MAAM;KAGnB,KAAK,MAAM,QAAQ,KAAA;KACnB,WAAW,UACT,cAAc,OAAO,OAAO,MAAM,OAAO,MAAM,CAAC;KAElD,CAAA;IACE;KACL,EAAA,CAAA,CAEa;IACT,EAAA,CAAA;;AAInB,SAAS,UAAU,EACjB,OACA,UACA,KAKc;AACd,QACE,oBAAC,MAAD;EACS;EACP,gBAAgB,SAAS;GACvB,MAAM,SAAS,WAAW,eAAe,KAAK;AAC9C,OAAI,OAAQ,UAAS,OAAO;;YAG9B,oBAAC,UAAD;GAAU,cAAY,EAAE,wBAAwB;aAC7C,cAAc,KAAK,WAClB,oBAAC,aAAD;IAAgC,OAAO,OAAO;cAC3C,EAAE,OAAO,SAAS;IACP,EAFI,OAAO,MAEX,CACd;GACO,CAAA;EACN,CAAA;;AAIX,SAAS,YAAY,EACnB,OACA,UACA,SACA,KAMc;CACd,MAAM,SAAS,OAAO;AAEtB,QACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,QAAD;IACE,eAAY;IACZ,WAAU;IACV,CAAA;GACF,oBAAC,OAAD;IACE,MAAK;IACE;IACP,WAAW,UAAU,SAAS,MAAM,OAAO,MAAM;IACjD,aAAa,EAAE,8BAA8B;IAC7C,cAAY,EAAE,8BAA8B;IAC5C,WAAA;IACA,oBAAkB,UAAU,SAAS,KAAA;IAGrC,WAAU;IACV,CAAA;GACD,SACC,oBAAC,QAAD;IACE,SAAQ;IACR,MAAK;IACL,cAAY,EAAE,wBAAwB;IACtC,eAAe,SAAS,GAAG;IAC3B,WAAU;cAEV,oBAAC,GAAD;KAAG,WAAU;KAAW,eAAY;KAAS,CAAA;IACtC,CAAA;GAEV,WACC,oBAAC,KAAD;IACE,IAAI;IACJ,WAAU;cAET,EAAE,6BAA6B,EAAE,OAAA,GAA0B,CAAC;IAC3D,CAAA;GAEF;;;AAIV,SAAS,aAAa,EACpB,UACA,UACA,SACA,KAMc;CACd,MAAM,QACJ,SAAS,WAAW,IAChB,EAAE,4BAA4B,GAC9B,EAAE,wBAAwB;AAEhC,QACE,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,qBAAD;EAAqB,SAAA;YACnB,qBAAC,QAAD;GACE,SAAQ;GACR,MAAK;GACL,WAAU;GACV,cAAY;aAJd,CAME,oBAAC,QAAD;IAAQ,WAAU;IAAW,eAAY;IAAS,CAAA,EACjD,SAAS,SAAS,KACjB,oBAAC,QAAD;IAAM,WAAU;cACb,SAAS;IACL,CAAA,CAEF;;EACW,CAAA,EACtB,qBAAC,qBAAD;EAAqB,OAAM;EAAM,WAAU;YAA3C,CACG,uBAAuB,KAAK,aAC3B,oBAAC,0BAAD;GAEE,SAAS,SAAS,SAAS,SAAS;GACpC,uBAAuB,SAAS,SAAS;GAEzC,WAAW,UAAU,MAAM,gBAAgB;aAE1C,EAAE,oBAAoB,UAAU;GACR,EAPpB,SAOoB,CAC3B,EACD,SAAS,SAAS,KACjB,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,uBAAD,EAAyB,CAAA,EAGzB,oBAAC,kBAAD;GAAkB,UAAU;aACzB,EAAE,yBAAyB;GACX,CAAA,CAClB,EAAA,CAAA,CAEe;IACT,EAAA,CAAA;;AAMnB,SAAS,eAA4B;AACnC,QACE,oBAAC,OAAD;EAAK,WAAU;EAAiB,eAAY;YACzC;GAAC;GAAG;GAAG;GAAG;GAAG;GAAE,CAAC,KAAK,QACpB,qBAAC,OAAD;GAAe,WAAU;aAAzB,CACE,oBAAC,UAAD,EAAU,WAAU,gCAAiC,CAAA,EACrD,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,UAAD,EAAU,WAAU,eAAgB,CAAA,EACpC,oBAAC,UAAD,EAAU,WAAU,aAAc,CAAA,CAC9B;MACF;KANI,IAMJ,CACN;EACE,CAAA;;AAIV,SAAS,WAAW,EAAE,KAAiC;AACrD,QACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,mBAAD;IACE,WAAU;IACV,eAAY;IACZ,CAAA;GACF,oBAAC,KAAD;IAAG,WAAU;cACV,EAAE,uBAAuB;IACxB,CAAA;GACJ,oBAAC,KAAD;IAAG,WAAU;cACV,EAAE,6BAA6B;IAC9B,CAAA;GACA;;;AAIV,SAAS,eAAe,EACtB,SACA,KAIc;AACd,QACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,QAAD;IAAQ,WAAU;IAA+B,eAAY;IAAS,CAAA;GACtE,oBAAC,KAAD;IAAG,WAAU;cACV,EAAE,4BAA4B;IAC7B,CAAA;GACJ,oBAAC,KAAD;IAAG,WAAU;cACV,EAAE,kCAAkC;IACnC,CAAA;GACJ,oBAAC,QAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,WAAU;IAAO,SAAS;cAC3D,EAAE,yBAAyB;IACrB,CAAA;GACL;;;AAIV,SAAS,WAAW,EAClB,SACA,KAIc;AACd,QACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,aAAD;IAAa,WAAU;IAA0B,eAAY;IAAS,CAAA;GACtE,oBAAC,KAAD;IAAG,WAAU;cACV,EAAE,uBAAuB;IACxB,CAAA;GACJ,oBAAC,QAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,WAAU;IAAO,SAAS;cAC3D,EAAE,uBAAuB;IACnB,CAAA;GACL;;;;;;;;;;;;;;AC34BV,SAAgB,eAAe,EAE7B,YACA,WACA,aACA,SACA,cAEA,GAAG,YACsC;AACzC,QACE,oBAAC,OAAD;EAAK,GAAI;YACP,oBAAC,oBAAD,EAAsB,CAAA;EAClB,CAAA;;AAIV,MAAa,+BAAqD;CAChE,YAAY;CACZ,aAAa;CACb,YAAY,CAAC;EAAE,IAAI;EAAW,OAAO;EAAW,CAAC;CACjD,QAAQ,EAAE;CACX"}