{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-AFE6WQFY.cjs","../src/components/shared/dev-section/dev-section-view.tsx","../src/components/shared/dev-section/dev-section-page.tsx","../src/components/shared/dev-section/dev-card-row.tsx","../src/components/shared/delivery/delivery-row.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-AFE6WQFY.cjs","sourcesContent":[null,"'use client';\n\n/**\n * DevSectionView — the canonical chrome for ANY dev-center section\n * (Roadmap / Delivery / Releases). One component, used in BOTH:\n *\n *   - tabbed `/roadmap-and-releases` (compact title mode, no `hero`)\n *   - full-page `/roadmap`, `/bug-fixes-and-enhancements`, `/releases`\n *     (hero mode with icon + description + back link)\n *\n * Owns: title rendering, the inline search input, the filter pill row,\n * and the URL-param wiring that connects both. The list `children`\n * receive a clean URL contract — they read `?<paramKey>=...` via\n * `useSearchParams()` and refetch on change. No duplicated controls.\n */\n\nimport type { ReactNode } from 'react';\nimport { useState, useEffect } from 'react';\nimport { useRouter, useSearchParams, usePathname } from '../../../embed-shims';\nimport { SearchInput } from '../../ui';\nimport { StatusFilterComponent } from '../../features';\nimport {\n  OPENFRAME_DEV_SECTIONS,\n  type OpenframeDevSection,\n  type OpenframeDevSectionKey,\n} from '../../../utils/dev-sections/openframe-dev-sections';\n\nexport interface DevSectionViewProps {\n  /** Which section to render — drives title, search, and filter\n   *  config via the `OPENFRAME_DEV_SECTIONS` registry. */\n  sectionKey: OpenframeDevSectionKey;\n  /** When set, renders the rich page-level hero (icon + h1 + description).\n   *  Omit for the compact tab-context heading. */\n  hero?: {\n    /** Pre-rendered icon JSX. Server components render the icon themselves\n     *  and pass the element here — function references can't cross the\n     *  server→client boundary, but React elements can. */\n    icon: ReactNode;\n    /** Hero title. Falls back to `OPENFRAME_DEV_SECTIONS[sectionKey].hero.title`\n     *  when omitted, so embedders can override the (OpenFrame-specific) default\n     *  copy without forking the registry. */\n    title?: string;\n    description: string;\n  };\n  /** Optional slot rendered BETWEEN the hero and the search/filter\n   *  controls. Use this for an entry-action surface that should sit\n   *  above the list (e.g. the Help Center's \"Open a new ticket\" form).\n   *  The slot is wrapped in the same `gap-10` flex column so spacing\n   *  matches the surrounding chrome — callers should NOT add their\n   *  own top/bottom margin. Renders `null` (no DOM) when omitted. */\n  preControls?: ReactNode;\n  /** The page-specific list body. Reads URL params written by this\n   *  component (search input + filter pills). */\n  children: ReactNode;\n  /** Render this component's own title heading (hero or compact `h2`). Default\n   *  `true` — the tab context renders the compact heading itself. Pass `false`\n   *  when the host already renders the title elsewhere (e.g. `DevSectionPage`\n   *  routes it through the unified `PageLayout` `TitleBlock`): then only the\n   *  search + filter controls + list render, with no duplicate heading. */\n  showHeading?: boolean;\n}\n\nexport function DevSectionView({ sectionKey, hero, preControls, children, showHeading = true }: DevSectionViewProps) {\n  const section = OPENFRAME_DEV_SECTIONS[sectionKey];\n  const router = useRouter();\n  const pathname = usePathname();\n  const searchParams = useSearchParams();\n\n  // Widen from the registry's per-section literal types to the interface —\n  // optional fields (`clearParamKeys`) exist on the interface but not on\n  // every section's inferred literal. Sound: the registry `satisfies`\n  // `Record<string, OpenframeDevSection>`.\n  const search: OpenframeDevSection['search'] = section.search;\n  const filter = section.filter;\n\n  const currentSearch = search ? searchParams.get(search.paramKey) || '' : '';\n  const currentFilterValue = filter\n    ? searchParams.get(filter.paramKey) || filter.defaultValue\n    : '';\n\n  // Controlled search-input state — TYPING commits to the URL only on\n  // Enter (not on every keystroke), but CLEARING commits immediately:\n  // an emptied input must not keep filtering the list (the URL param —\n  // and any linked `clearParamKeys` companion like the tickets\n  // `?ticket=` drawer param — previously survived until Enter).\n  // Lazy init from URL avoids a brief flash of stale value on first\n  // paint after URL-driven re-render (e.g. tab switch).\n  const [searchValue, setSearchValue] = useState(() => currentSearch);\n  useEffect(() => {\n    setSearchValue(currentSearch);\n  }, [currentSearch]);\n\n  const handleSearchSubmit = (value: string) => {\n    if (!search) return;\n    const params = new URLSearchParams(searchParams.toString());\n    if (value.trim()) {\n      params.set(search.paramKey, value.trim());\n    } else {\n      params.delete(search.paramKey);\n      // Empty commit tears down the search's linked companion params too\n      // (deep links set them TOGETHER — see the config field docs).\n      for (const key of search.clearParamKeys ?? []) params.delete(key);\n    }\n    const qs = params.toString();\n    router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });\n  };\n\n  const handleSearchChange = (value: string) => {\n    setSearchValue(value);\n    if (value === '' && currentSearch !== '') handleSearchSubmit('');\n  };\n\n  const handleFilterChange = (value: string) => {\n    if (!filter) return;\n    const params = new URLSearchParams(searchParams.toString());\n    if (value === filter.defaultValue) params.delete(filter.paramKey);\n    else params.set(filter.paramKey, value);\n    router.replace(`${pathname}?${params.toString()}`, { scroll: false });\n  };\n\n  return (\n    <div className=\"w-full flex flex-col gap-10\">\n      {showHeading &&\n        (hero ? (\n          <div className=\"space-y-4\">\n            <h1 className=\"text-h1 tracking-[-1.12px] text-ods-text-primary flex items-center gap-3\">\n              {hero.icon}\n              {hero.title ?? section.hero.title}\n            </h1>\n            <p className=\"text-h4 text-ods-text-secondary max-w-3xl\">\n              {hero.description}\n            </p>\n          </div>\n        ) : (\n          <div className=\"flex items-center justify-between w-full\">\n            <h2 className=\"font-['Azeret_Mono'] font-semibold text-[32px] md:text-[40px] lg:text-[48px] leading-[40px] md:leading-[48px] lg:leading-[56px] text-ods-text-primary tracking-[-0.64px] md:tracking-[-0.8px] lg:tracking-[-0.96px]\">\n              {section.hero.title}\n              <span className=\"text-ods-accent\">:</span>\n            </h2>\n          </div>\n        ))}\n\n      {preControls}\n\n      {(search || filter) && (\n        <div className=\"space-y-4\">\n          {search && (\n            <SearchInput\n              showDropdown={false}\n              placeholder={search.placeholder}\n              value={searchValue}\n              onChange={handleSearchChange}\n              onSubmit={handleSearchSubmit}\n            />\n          )}\n          {filter && (\n            <StatusFilterComponent\n              selectedStatus={currentFilterValue}\n              onStatusChange={handleFilterChange}\n              statusOptions={[...filter.options]}\n            />\n          )}\n        </div>\n      )}\n\n      {children}\n    </div>\n  );\n}\n","'use client';\n\n/**\n * DevSectionPage — full-page wrapper for a dev-center section\n * (`/roadmap`, `/bug-fixes-and-enhancements`, `/releases`).\n *\n * Mounts the lib's canonical `PageLayout` directly (no in-app wrapper)\n * so the back-button affordance stays in lockstep with whatever the\n * design system ships — any future lib change to BackButton / TitleBlock\n * propagates automatically.\n *\n * Composition: `PageShell` → `PageLayout` (back-to-home wired) →\n * `DevSectionView` (icon hero + search + filter pills) → list body.\n *\n * Adding a new section is one entry in `OPENFRAME_DEV_SECTIONS` plus a\n * single-line page file mounting this factory with the new key.\n */\n\nimport type { ReactNode } from 'react';\nimport { useRouter } from '../../../embed-shims/next-navigation';\nimport { PageShell, PageLayout } from '../../ui';\nimport { DevSectionView } from './dev-section-view';\nimport {\n  OPENFRAME_DEV_SECTIONS,\n  type OpenframeDevSectionKey,\n} from '../../../utils/dev-sections/openframe-dev-sections';\n\nexport interface DevSectionPageProps {\n  sectionKey: OpenframeDevSectionKey;\n  /** The page-specific list body (e.g. `<RoadmapList />`). */\n  children: ReactNode;\n  /** Optional slot rendered BETWEEN the hero and search/filter — see\n   *  `DevSectionView.preControls`. Used by surfaces that want an entry\n   *  action (e.g. Help Center's \"Open a new ticket\" form) above the\n   *  controls instead of below them. */\n  preControls?: ReactNode;\n  /** Back-button config — same shape as `LegalDocumentPage` /\n   *  `ReleaseDetailPage`. Pass `false` to hide entirely. Default\n   *  `{ label: 'Back to home', href: '/' }` — embedders whose \"home\" isn't `/`\n   *  should override `href`, or pass `false` if the embed has no home page. */\n  backButton?: { label?: string; href?: string } | false;\n  /** Override the hero title. Defaults to the (OpenFrame-specific) copy in\n   *  `OPENFRAME_DEV_SECTIONS[sectionKey].hero.title`. Set this to brand the\n   *  section for a non-OpenFrame embed. */\n  title?: string;\n  /** Override the hero subtitle/description. Defaults to\n   *  `OPENFRAME_DEV_SECTIONS[sectionKey].hero.description`. */\n  subtitle?: string;\n  /** Render the standalone `<PageShell>` (own `<main>` + bg + max-width). Default\n   *  `true` — the contract for marketing/hub surfaces with no app shell. Pass\n   *  `false` when the host layout already provides the page container (e.g.\n   *  openframe-frontend's `AppLayout` `<main>`): then only the\n   *  `page-shell-content` padding box is rendered, avoiding a nested `<main>`. */\n  shell?: boolean;\n}\n\nexport function DevSectionPage({\n  sectionKey,\n  children,\n  preControls,\n  backButton,\n  title,\n  subtitle,\n  shell = true,\n}: DevSectionPageProps) {\n  const router = useRouter();\n  const section = OPENFRAME_DEV_SECTIONS[sectionKey];\n\n  // Back-button config — mirrors LegalDocumentPage / ReleaseDetailPage.\n  // Default: { label: 'Back to home', href: '/' }. Pass `false` to hide.\n  // After `backButton &&` narrowing, inner type is `{ label?, href? } |\n  // undefined`; don't re-compare to `false` (TS2367).\n  const backCfg =\n    backButton === false\n      ? undefined\n      : {\n          label: (backButton ? backButton.label : undefined) ?? 'Back to home',\n          onClick: () => router.push((backButton ? backButton.href : undefined) ?? '/'),\n        };\n\n  const inner = (\n    // Unified header: title/description route through the canonical (frozen)\n    // `PageLayout` `TitleBlock` (text-h2) — same as FAQ / Legal / detail pages —\n    // so every help-center surface shares one header. `DevSectionView` then\n    // renders ONLY its search + filter controls (`showHeading={false}`), no\n    // duplicate title. (The hero icon is intentionally dropped: TitleBlock is\n    // frozen and renders title-only.)\n    <PageLayout\n      title={title ?? section.hero.title}\n      subtitle={subtitle ?? section.hero.description}\n      titleSize=\"h1\"\n      titleWrap\n      backButton={backCfg}\n    >\n      <DevSectionView sectionKey={sectionKey} showHeading={false} preControls={preControls}>\n        {children}\n      </DevSectionView>\n    </PageLayout>\n  );\n\n  // `shell` true → standalone `<PageShell>` (own <main> + bg + max-width).\n  // false → padding-only box (no nested <main>) for hosts whose layout already\n  // provides the container; both consume the host's `--page-shell-*` vars.\n  return shell ? <PageShell>{inner}</PageShell> : <div className=\"page-shell-content\">{inner}</div>;\n}\n","'use client';\n\n/**\n * Shared row chrome for any `DevSectionPage` list (delivery, tickets,\n * future sections). One source of truth for the layout that every\n * dev-section card row uses:\n *   left column  → title (h3) / subtitle (h5 uppercase) / description\n *                  (h4 line-clamp-3), each in a fixed min-height block\n *                  so rows align across the grid\n *   right column → caller-supplied stacked badges\n *\n * Surface stays small on purpose — `rightBadges` is a `ReactNode` so\n * the caller decides how many badges (delivery: 2, tickets: 1-2,\n * future: anything). No behavior baked in: the caller wraps the row\n * in a `<div>` (static, like delivery) or `<button>` (clickable, like\n * tickets) and renders the row content via this component.\n *\n * Pair with `DevCardRowSkeletonList` for the loading state — the\n * skeleton mirrors the same min-heights so the in-flight UI doesn't\n * shift the layout when real data lands.\n *\n * NOTE: the ticket conversation row is NOT here — it renders the shared\n * `<ChatMessageRow>` (`components/chat/chat-message-row.tsx`), the SAME\n * component the OpenMSP Slack-community feed uses, so the two surfaces stay\n * pixel-identical by construction.\n */\n\nimport type { ReactNode } from 'react';\n\nexport interface DevCardRowContentProps {\n  title: string;\n  /** Single-line uppercase metadata (e.g. \"UPDATED today, #4271, Code review\"). */\n  subtitle: string;\n  /** 3-line description block. Empty string renders the fallback. */\n  description: string;\n  /** Fallback copy when `description` is empty. Defaults to a generic\n   *  string; ticket / delivery surfaces override. */\n  emptyDescription?: string;\n  /** Right column — caller renders its own stacked badges. */\n  rightBadges: ReactNode;\n}\n\nexport function DevCardRowContent({\n  title,\n  subtitle,\n  description,\n  emptyDescription = 'No description provided',\n  rightBadges,\n}: DevCardRowContentProps) {\n  return (\n    <div className=\"flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full\">\n      <div className=\"flex-1 min-w-0 w-full md:w-auto flex flex-col gap-[12px] md:gap-[16px]\">\n        <div className=\"min-h-[24px] flex items-center\">\n          <h3 className=\"text-h3 text-ods-text-primary tracking-[-0.36px] flex-1 line-clamp-2 md:truncate break-words\">\n            {title}\n          </h3>\n        </div>\n        <div className=\"min-h-[20px] flex items-center\">\n          <p className=\"text-h5 text-ods-text-secondary uppercase tracking-[-0.28px] truncate\">\n            {subtitle}\n          </p>\n        </div>\n        <div className=\"min-h-[72px] flex items-center\">\n          <p className=\"text-h4 text-ods-text-secondary line-clamp-3 break-words\">\n            {description || emptyDescription}\n          </p>\n        </div>\n      </div>\n      <div className=\"flex-shrink-0 self-start flex flex-col gap-2\">\n        {rightBadges}\n      </div>\n    </div>\n  );\n}\n\n/**\n * Skeleton rendering for a single row — the bars mirror the same\n * min-heights as `DevCardRowContent` so the loading→loaded swap\n * doesn't reflow.\n */\nexport function DevCardRowSkeleton() {\n  return (\n    <div className=\"border-b border-ods-border last:border-b-0 p-[12px] md:p-[16px]\">\n      <div className=\"flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full\">\n        <div className=\"flex-1 min-w-0 w-full md:w-auto flex flex-col gap-[12px] md:gap-[16px]\">\n          <div className=\"min-h-[24px] flex items-center\">\n            <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-full\" />\n          </div>\n          <div className=\"min-h-[20px] flex items-center\">\n            <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-1/2\" />\n          </div>\n          <div className=\"min-h-[72px] flex items-center\">\n            <div className=\"flex-1 space-y-1\">\n              <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-full\" />\n              <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-full\" />\n              <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-2/3\" />\n            </div>\n          </div>\n        </div>\n        <div className=\"flex-shrink-0 self-start flex flex-col gap-2\">\n          <div className=\"h-[32px] w-[100px] bg-ods-border rounded animate-pulse\" />\n          <div className=\"h-[32px] w-[120px] bg-ods-border rounded animate-pulse\" />\n        </div>\n      </div>\n    </div>\n  );\n}\n\n/**\n * The standard \"5 skeleton rows inside a bordered card\" loading state\n * used by every list shell. Both delivery (`delivery-table.tsx`) and\n * tickets (`tickets-list.tsx`) mount this directly.\n */\nexport function DevCardRowSkeletonList({ rows = 5 }: { rows?: number }) {\n  return (\n    <div className=\"bg-ods-card border border-ods-border rounded-[6px] overflow-hidden w-full\">\n      {Array.from({ length: rows }, (_, i) => (\n        <DevCardRowSkeleton key={i} />\n      ))}\n    </div>\n  );\n}\n","'use client'\n\n/**\n * `<DeliveryRow />` — canonical single-row presentation for a ClickUp\n * delivery item.\n *\n * Single source of truth: both the `/bug-fixes-and-enhancements` page\n * (via `DeliveryTable`) AND the linked-delivery card on a HubSpot ticket\n * (via `TicketLinkedDeliveryCard`) compose this primitive. Visual parity\n * across those two surfaces is the design goal — the user reads the\n * card on their ticket and recognises it as a row from the public\n * delivery list.\n *\n * Behaviors:\n *   - `href` set → outer element is an `<a>`, the whole row becomes\n *     clickable (used by the linked-card surface to deep-link into\n *     `/bug-fixes-and-enhancements?focus=<id>`).\n *   - `id` set → outer element gets that DOM id so the consuming page\n *     can `scrollIntoView` to it when the URL carries `?focus=<id>`.\n *   - `highlighted` true → brief accent border + background pulse\n *     (`animate-flash-focus` keyframe defined in `tailwind.config.ts`).\n *   - `caption` set → small uppercase label rendered above the title\n *     (\"LINKED DELIVERY\" on the ticket-side variant). Omitted on the\n *     standard list rendering.\n */\n\nimport * as React from 'react'\nimport Link from '../../../embed-shims/next-link'\nimport { StatusBadge } from '../../ui/status-badge'\nimport { AvatarStack } from '../../ui/avatar-stack'\nimport { getStatusColorScheme } from '../../../utils'\nimport {\n  type DeliveryItem,\n  TASK_TYPE_LABELS,\n  TASK_TYPE_TEXT_COLORS,\n} from '../../../types/delivery'\nimport { cn } from '../../../utils/cn'\n\n/** Same heuristic as DeliveryTable's local helper. Inlined so the row\n *  primitive owns its complete rendering contract. */\nfunction getRelativeTime(timestamp: number): string {\n  const now = Date.now()\n  const diff = now - timestamp\n  const days = Math.floor(diff / (1000 * 60 * 60 * 24))\n  const weeks = Math.floor(days / 7)\n  const months = Math.floor(days / 30)\n  if (months > 0) return months === 1 ? 'last month' : `${months} months ago`\n  if (weeks > 0) return weeks === 1 ? 'last week' : `${weeks} weeks ago`\n  if (days > 0) return days === 1 ? 'yesterday' : `${days} days ago`\n  return 'today'\n}\n\nexport interface DeliveryRowProps {\n  item: DeliveryItem\n  /** When set, the row becomes a clickable anchor. The ticket-side\n   *  linked-card composes this from `buildDevSectionUrl('delivery', id)`\n   *  which carries `?search=<id>` — the delivery list filters to that\n   *  exact task on landing (canonical deep-link mechanism, same one\n   *  the chat-inline delivery card uses). */\n  href?: string\n  /** Small uppercase caption rendered above the title. Used by the\n   *  linked-delivery card variant (\"LINKED DELIVERY\"). */\n  caption?: string\n  /** DOM `id` applied to the row's outer element. `DeliveryTable`\n   *  always sets `delivery-<external_id>` so chat-card deep-links\n   *  (`?search=<id>#delivery-<id>`) and the ticket linked-card path\n   *  both have a target for `useScrollToHash` to scroll to. Always\n   *  paired with `scroll-mt-24` on the outer element so the row lands\n   *  BELOW the sticky chrome after the scroll. */\n  id?: string\n  /** `'_blank'` renders a plain external anchor (new tab, `rel` hardened) —\n   *  for rows whose href leaves the app (e.g. the raw ClickUp task URL on the\n   *  design-doc surfaces). Internal deep-links omit it and keep the Link shim. */\n  target?: '_blank'\n  className?: string\n}\n\nexport function DeliveryRow({\n  item,\n  href,\n  target,\n  caption,\n  id,\n  className,\n}: DeliveryRowProps) {\n  const taskType = item.taskType as keyof typeof TASK_TYPE_LABELS\n  const typeBadgeLabel = TASK_TYPE_LABELS[taskType] || 'TASK'\n  const typeBadgeTextColor = TASK_TYPE_TEXT_COLORS[taskType] || ''\n  const statusBadgeScheme = getStatusColorScheme(item.status)\n  const relativeTime = getRelativeTime(item.dateUpdated)\n  const subtitle = `ACTIVE ${relativeTime}${item.listNames.length > 0 ? `, ${item.listNames.join(', ')}` : ''}, ${item.id}`\n\n  const inner = (\n    <div className=\"flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full\">\n      {/* Left: caption (optional) + title + subtitle + description */}\n      <div className=\"flex-1 min-w-0 w-full md:w-auto flex flex-col gap-[12px] md:gap-[16px]\">\n        {caption && (\n          <p className=\"text-h5 text-ods-text-secondary\">\n            {caption}\n          </p>\n        )}\n        <div className=\"min-h-[24px] md:min-h-[24px] flex items-center\">\n          <h3 className=\"text-h3 text-ods-text-primary tracking-[-0.36px] flex-1 line-clamp-2 md:truncate break-words\">\n            {item.title}\n          </h3>\n        </div>\n        <div className=\"min-h-[20px] flex items-center\">\n          {/* Relative time is wall-clock-derived: on an SSR host the server and\n              client render different strings a moment apart — text-only, so\n              suppress instead of forcing a client-only render. */}\n          <p className=\"text-h5 text-ods-text-secondary uppercase tracking-[-0.28px] truncate\" suppressHydrationWarning>\n            {subtitle}\n          </p>\n        </div>\n        <div className=\"min-h-[72px] flex items-center\">\n          <p className=\"text-h4 text-ods-text-secondary line-clamp-3 break-words\">\n            {item.description || 'No description provided'}\n          </p>\n        </div>\n      </div>\n\n      {/* Right (desktop): badges + assignee stack in a right-aligned\n          column. Mobile: ONE horizontal footer row — badges lead,\n          avatars anchor the right edge (kanban-card convention: the\n          assignee owns the trailing slot of the meta row; a column of\n          right-aligned orphans under the description reads as clutter\n          on small screens). */}\n      <div className=\"flex-shrink-0 self-stretch md:self-start w-full md:w-auto flex flex-row md:flex-col items-center md:items-end gap-2\">\n        <StatusBadge\n          text={item.status.toUpperCase()}\n          colorScheme={statusBadgeScheme}\n          variant=\"card\"\n          className=\"border border-ods-border\"\n        />\n        <StatusBadge\n          text={typeBadgeLabel}\n          variant=\"card\"\n          className={`border border-ods-border ${typeBadgeTextColor}`}\n        />\n        {item.assignees && item.assignees.length > 0 ? (\n          <AvatarStack\n            size=\"xs\"\n            className=\"ml-auto md:ml-0\"\n            people={item.assignees.map((a) => ({\n              key: a.id,\n              name: a.name ?? 'Unknown',\n              avatarUrl: a.avatarUrl,\n            }))}\n          />\n        ) : null}\n      </div>\n    </div>\n  )\n\n  const baseClass = cn(\n    'block p-[12px] md:p-[16px] no-underline text-inherit transition-colors duration-150',\n    // `scroll-mt-24` is paid for whether `id` is set or not (it's a\n    // single Tailwind utility, no runtime cost). Keeping it\n    // unconditional means a future caller adding `id` doesn't also\n    // have to remember to ask for the offset.\n    'scroll-mt-24',\n    href && 'hover:bg-ods-bg-hover cursor-pointer',\n    className,\n  )\n\n  if (href && target === '_blank') {\n    return (\n      <a href={href} id={id} className={baseClass} target=\"_blank\" rel=\"noopener noreferrer\">\n        {inner}\n      </a>\n    )\n  }\n\n  if (href) {\n    // `Link` is the env-aware embed-shim — delegates to `next/link` on\n    // a Next.js host (soft RSC nav, back-button restores the previous\n    // page's React state intact), falls back to a plain `<a>` on\n    // non-Next embedders. A raw `<a href>` was hard-navigating +\n    // losing TanStack-Query state on back, leaving /tickets stuck on\n    // its skeleton.\n    return (\n      <Link href={href} id={id} className={baseClass} prefetch={false}>\n        {inner}\n      </Link>\n    )\n  }\n\n  return <div id={id} className={baseClass}>{inner}</div>\n}\n"]}