{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "timeline",
  "type": "registry:ui",
  "title": "Timeline",
  "description": "Accessible activity timeline with flow and canvas layouts, grouped events and expandable details.",
  "dependencies": [
    "lucide-react@^1.47.0"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/timeline/timeline.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/timeline/timeline.tsx",
      "content": "import {\n  forwardRef,\n  Fragment,\n  useCallback,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type AnimationEvent,\n  type CSSProperties,\n  type FocusEvent,\n  type ForwardedRef,\n  type HTMLAttributes,\n  type KeyboardEvent,\n  type MouseEvent,\n  type PointerEvent,\n  type ReactElement,\n  type ReactNode,\n  type RefAttributes,\n  type WheelEvent,\n} from \"react\";\nimport {\n  Check,\n  Circle,\n  CircleDot,\n  Clock3,\n  TriangleAlert,\n  X,\n} from \"lucide-react\";\nimport { useHydrated } from \"../../utils/use-hydrated\";\nimport { cn } from \"../../utils/cn\";\nimport {\n  clampTimelineRevealCount,\n  fitTimelineTransform,\n  getDefaultTimelineTransform,\n  getEdgeEnabledTimelineItemId,\n  getNextEnabledTimelineItemId,\n  getTimelineContentSize,\n  getTimelineRevealBatchDuration,\n  isTimelineRevealActive,\n  normalizeTimelineItems,\n  normalizeTimelineRevealOptions,\n  normalizeViewportOptions,\n  resolveTimelinePresentation,\n  timelineGeometry,\n  zoomTimelineTransform,\n  type NormalizedTimelineItem,\n  type NormalizedTimelineRevealOptions,\n  type TimelineItemData,\n  type TimelineItemPayload,\n  type TimelineLayout,\n  type TimelineMode,\n  type TimelineOrder,\n  type TimelineOrientation,\n  type TimelinePoint,\n  type TimelinePresentation,\n  type TimelineRevealMode,\n  type TimelineRevealOptions,\n  type TimelineScale,\n  type TimelineControlsVisibility,\n  type TimelineStatus,\n  type TimelineTransform,\n  type TimelineVariant,\n  type TimelineGroup,\n  type TimelineViewportChrome,\n  type TimelineViewportOptions,\n} from \"./timeline-utils\";\n\nexport type {\n  NormalizedTimelineItem,\n  NormalizedTimelineRevealOptions,\n  TimelineImage,\n  TimelineItemBaseData,\n  TimelineItemData,\n  TimelineItemDefaultContent,\n  TimelineItemPayload,\n  TimelineLayout,\n  TimelineMode,\n  TimelineOrder,\n  TimelineOrientation,\n  TimelinePoint,\n  TimelinePresentation,\n  TimelineRevealMode,\n  TimelineRevealOptions,\n  TimelineRevealTrigger,\n  TimelineScale,\n  TimelineControlsVisibility,\n  TimelineStatus,\n  TimelineTransform,\n  TimelineViewportChrome,\n  TimelineViewportOptions,\n  TimelineWheelZoom,\n  TimelineVariant,\n  TimelineGroup,\n} from \"./timeline-utils\";\n\nexport type TimelineItemRenderer<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n> = (item: TimelineItemData<TPayload>) => ReactNode;\n\nexport interface TimelineProps<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n> extends Omit<HTMLAttributes<HTMLElement>, \"children\"> {\n  items: TimelineItemData<TPayload>[];\n  mode?: TimelineMode;\n  orientation?: TimelineOrientation;\n  layout?: TimelineLayout;\n  scale?: TimelineScale;\n  order?: TimelineOrder;\n  interactive?: boolean;\n  presentation?: TimelinePresentation;\n  reveal?: TimelineRevealMode;\n  revealOptions?: TimelineRevealOptions;\n  revealCount?: number;\n  onItemReveal?: (id: string, index: number) => void;\n  onRevealComplete?: () => void;\n  viewport?: TimelineViewportOptions;\n  selectedId?: string | null;\n  defaultSelectedId?: string | null;\n  onSelectedIdChange?: (id: string | null) => void;\n  renderItem?: TimelineItemRenderer<TPayload>;\n  variant?: TimelineVariant;\n  renderDetails?: TimelineItemRenderer<TPayload>;\n  expandedIds?: string[];\n  defaultExpandedIds?: string[];\n  onExpandedIdsChange?: (ids: string[]) => void;\n  getGroup?: (item: TimelineItemData<TPayload>) => TimelineGroup | null;\n}\n\nexport interface TimelineItemProps<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n> extends Omit<HTMLAttributes<HTMLLIElement>, \"onSelect\"> {\n  item: NormalizedTimelineItem<TPayload>;\n  mode?: TimelineMode;\n  orientation?: TimelineOrientation;\n  layout?: TimelineLayout;\n  presentation?: TimelinePresentation;\n  interactive?: boolean;\n  selected?: boolean;\n  renderItem?: TimelineItemRenderer<TPayload>;\n  onSelect?: (id: string) => void;\n  variant?: TimelineVariant;\n  details?: ReactNode;\n  expanded?: boolean;\n  onExpandedChange?: () => void;\n}\n\nexport interface TimelineViewportProps extends Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"onSelect\"\n> {\n  children: ReactNode;\n  contentSize: { width: number; height: number };\n  interactive?: boolean;\n  orientation?: TimelineOrientation;\n  layout?: TimelineLayout;\n  presentation?: TimelinePresentation;\n  viewport?: TimelineViewportOptions;\n  selectedPoint?: TimelinePoint;\n  onNavigate?: (id: string | null) => void;\n  getPreviousId?: () => string | null;\n  getNextId?: () => string | null;\n  getFirstId?: () => string | null;\n  getLastId?: () => string | null;\n}\n\nexport interface TimelineControlsProps extends HTMLAttributes<HTMLDivElement> {\n  zoom: number;\n  minZoom: number;\n  maxZoom: number;\n  visibility?: TimelineControlsVisibility;\n  visible?: boolean;\n  onZoomIn: () => void;\n  onZoomOut: () => void;\n  onReset: () => void;\n  onFit: () => void;\n}\n\nconst timelineRootClasses = \"w-full text-foreground\";\n\nconst timelineViewportClasses =\n  \"relative min-h-[22rem] overflow-hidden rounded-md bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[interactive=true]:cursor-grab data-[dragging=true]:cursor-grabbing data-[layout=alternating]:min-h-[36rem] data-[orientation=vertical]:min-h-[30rem] data-[orientation=horizontal]:touch-pan-y data-[orientation=vertical]:touch-pan-x\";\n\nconst timelineViewportContentClasses =\n  \"absolute left-0 top-0 origin-top-left motion-safe:transition-transform motion-safe:duration-300 motion-safe:ease-out motion-reduce:transition-none data-[dragging=true]:transition-none\";\n\nconst timelineListClasses =\n  \"absolute left-0 top-0 m-[var(--dt-space-0)] list-none p-[var(--dt-space-0)]\";\n\nconst timelineStoryViewportClasses =\n  \"relative w-full min-w-0 overflow-hidden rounded-md bg-transparent [container-type:inline-size] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\";\n\nconst timelineStoryListClasses =\n  \"relative m-[var(--dt-space-0)] list-none p-[var(--dt-space-0)] before:absolute before:bottom-[var(--dt-space-2)] before:left-7 before:top-[var(--dt-space-2)] before:w-px before:rounded-full before:bg-timeline-rail before:content-[''] forced-colors:before:bg-[CanvasText]\";\n\nconst timelineStoryListLayoutClasses: Record<\n  \"default\" | \"alternating\",\n  string\n> = {\n  default: \"sm:before:left-9\",\n  alternating: \"sm:before:left-1/2 sm:before:-translate-x-1/2\",\n};\n\nconst timelineFlowListClasses =\n  \"relative m-[var(--dt-space-0)] list-none p-[var(--dt-space-0)] before:absolute before:bottom-[var(--dt-space-2)] before:left-6 before:top-[var(--dt-space-2)] before:w-px before:rounded-full before:bg-timeline-rail before:content-[''] forced-colors:before:bg-[CanvasText]\";\n\nconst timelineFlowListLayoutClasses: Record<\"default\" | \"alternating\", string> =\n  {\n    default: \"\",\n    alternating: \"sm:before:left-1/2 sm:before:-translate-x-1/2\",\n  };\n\nconst timelineRailClasses =\n  \"absolute rounded-full bg-timeline-rail forced-colors:bg-[CanvasText]\";\n\nconst timelineItemClasses = \"absolute left-0 top-0\";\n\nconst timelineStoryItemClasses =\n  \"relative grid min-w-0 grid-cols-[3.5rem_minmax(0,1fr)] pb-20 last:pb-0 sm:pb-16\";\n\nconst timelineStoryItemLayoutClasses: Record<\n  \"default\" | \"alternating\",\n  string\n> = {\n  default: \"sm:grid-cols-[4.5rem_minmax(0,29rem)]\",\n  alternating: \"sm:grid-cols-[minmax(0,1fr)_4.5rem_minmax(0,1fr)]\",\n};\n\nconst timelineFlowItemClasses =\n  \"relative grid min-w-0 grid-cols-[3rem_minmax(0,1fr)] gap-x-[var(--dt-space-3)] pb-8 last:pb-0\";\n\nconst timelineFlowItemLayoutClasses: Record<\"default\" | \"alternating\", string> =\n  {\n    default: \"\",\n    alternating:\n      \"sm:grid-cols-[minmax(0,1fr)_3rem_minmax(0,1fr)] sm:gap-x-[var(--dt-space-4)]\",\n  };\n\nconst timelineCardBaseClasses =\n  \"text-left text-foreground motion-safe:transition-[border-color,background-color,box-shadow] motion-safe:duration-200 data-[disabled=true]:opacity-55\";\n\nconst timelineViewportCardClasses =\n  \"absolute w-[var(--timeline-card-width,20rem)] rounded-md border border-timeline-border bg-background p-[var(--dt-space-4)] shadow-sm data-[interactive=true]:hover:shadow-md data-[selected=true]:border-ring data-[selected=true]:bg-muted/40 data-[selected=true]:shadow-md data-[selected=true]:ring-1 data-[selected=true]:ring-ring/35\";\n\nconst timelineStoryCardClasses =\n  \"relative col-start-2 row-start-1 min-w-0 max-w-full border-0 bg-transparent p-[var(--dt-space-0)] shadow-none\";\n\nconst timelineStoryCardAlternatingClasses: Record<\"start\" | \"end\", string> = {\n  start: \"sm:col-start-1 sm:justify-self-end sm:text-right\",\n  end: \"sm:col-start-3 sm:justify-self-start sm:text-left\",\n};\n\nconst timelineFlowCardClasses =\n  \"relative col-start-2 row-start-1 min-w-0 max-w-full rounded-md border border-timeline-border bg-background p-[var(--dt-space-4)] shadow-sm data-[interactive=true]:hover:shadow-md data-[selected=true]:border-ring data-[selected=true]:bg-muted/40 data-[selected=true]:shadow-md data-[selected=true]:ring-1 data-[selected=true]:ring-ring/35\";\n\nconst timelineFlowCardAlternatingClasses: Record<\"start\" | \"end\", string> = {\n  start: \"sm:col-start-1 sm:justify-self-end sm:text-right\",\n  end: \"sm:col-start-3 sm:justify-self-start sm:text-left\",\n};\n\nconst timelineMarkerBaseClasses =\n  \"z-10 flex size-8 items-center justify-center rounded-full border-2 text-xs font-medium shadow-sm motion-safe:transition-[border-color,background-color,box-shadow,transform] motion-safe:duration-200\";\n\nconst timelineViewportMarkerClasses =\n  \"absolute -translate-x-1/2 -translate-y-1/2\";\n\nconst timelineStoryMarkerClasses =\n  \"relative col-start-1 row-start-1 mt-[var(--dt-space-1)] size-7 justify-self-center border-background bg-background text-primary ring-1 ring-border\";\n\nconst timelineStoryMarkerAlternatingClasses = \"sm:col-start-2\";\n\nconst timelineFlowMarkerClasses =\n  \"relative col-start-1 row-start-1 mt-[var(--dt-space-1)] justify-self-center\";\n\nconst timelineFlowMarkerAlternatingClasses = \"sm:col-start-2\";\n\nconst timelineMarkerStatusClasses: Record<TimelineStatus, string> = {\n  neutral: \"border-timeline-border bg-background text-muted-foreground\",\n  complete: \"border-success bg-success text-success-foreground\",\n  current:\n    \"border-primary bg-primary text-primary-foreground ring-4 ring-primary/15\",\n  upcoming: \"border-timeline-border bg-muted text-muted-foreground\",\n  warning: \"border-warning bg-warning text-warning-foreground\",\n  error: \"border-destructive bg-destructive text-destructive-foreground\",\n};\n\nconst timelineStoryMarkerStatusClasses: Record<TimelineStatus, string> = {\n  neutral: \"text-muted-foreground\",\n  complete: \"text-primary\",\n  current: \"text-primary ring-primary/30\",\n  upcoming: \"text-muted-foreground\",\n  warning: \"text-warning\",\n  error: \"text-destructive\",\n};\n\nconst timelineStatusLabel: Record<TimelineStatus, string> = {\n  neutral: \"Neutral\",\n  complete: \"Complete\",\n  current: \"Current\",\n  upcoming: \"Upcoming\",\n  warning: \"Warning\",\n  error: \"Error\",\n};\n\nconst timelineControlButtonClasses =\n  \"inline-flex size-8 items-center justify-center rounded-md border border-timeline-border bg-background text-foreground shadow-sm motion-safe:transition-[background-color,border-color,box-shadow,transform] motion-safe:duration-150 hover:border-ring/50 hover:bg-muted hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring active:translate-y-px disabled:pointer-events-none disabled:opacity-50\";\n\nconst timelineViewportChromeClasses: Record<TimelineViewportChrome, string> = {\n  none: \"\",\n  subtle: \"bg-muted/20\",\n  panel: \"border border-timeline-border bg-muted/20 shadow-sm\",\n};\n\nconst timelineControlsVisibilityClasses: Record<\n  TimelineControlsVisibility,\n  string\n> = {\n  always: \"opacity-100\",\n  hover:\n    \"data-[visible=false]:pointer-events-none data-[visible=false]:translate-y-1 data-[visible=false]:opacity-0 data-[visible=true]:pointer-events-auto data-[visible=true]:translate-y-0 data-[visible=true]:opacity-100\",\n};\n\ntype TimelineContentStyle = CSSProperties & {\n  \"--timeline-transform\"?: string;\n};\n\nfunction getTimelineDateLabel(item: NormalizedTimelineItem) {\n  if (item.dateLabel !== undefined) {\n    return item.dateLabel;\n  }\n\n  if (item.datetime instanceof Date && !Number.isNaN(item.datetime.getTime())) {\n    return item.datetime.toLocaleString();\n  }\n\n  if (typeof item.datetime === \"string\") {\n    return item.datetime;\n  }\n\n  return null;\n}\n\nconst timelineStatusIcons = {\n  neutral: Circle,\n  complete: Check,\n  current: CircleDot,\n  upcoming: Clock3,\n  warning: TriangleAlert,\n  error: X,\n};\n\n// Only primary selection controls participate; embedded inputs and disclosures\n// retain their own keyboard behavior. DOM order also handles contiguous groups.\nfunction navigateTimeline(\n  event: KeyboardEvent<HTMLDivElement>,\n  onNavigate?: (id: string | null) => void,\n) {\n  const target = event.target as HTMLElement;\n  if (\n    target !== event.currentTarget &&\n    target.dataset.slot !== \"timeline-card-action\"\n  )\n    return false;\n  const keys = [\n    \"ArrowRight\",\n    \"ArrowLeft\",\n    \"ArrowDown\",\n    \"ArrowUp\",\n    \"Home\",\n    \"End\",\n  ];\n  if (!keys.includes(event.key)) return false;\n  const buttons = Array.from(\n    event.currentTarget.querySelectorAll<HTMLButtonElement>(\n      'button[data-slot=\"timeline-card-action\"]:not(:disabled)',\n    ),\n  );\n  if (!buttons.length) return false;\n  const active = buttons.indexOf(target as HTMLButtonElement);\n  const selected = buttons.findIndex(\n    (button) => button.getAttribute(\"aria-pressed\") === \"true\",\n  );\n  const index = active >= 0 ? active : selected;\n  const rtl = getComputedStyle(event.currentTarget).direction === \"rtl\";\n  let direction = event.key === \"ArrowLeft\" || event.key === \"ArrowUp\" ? -1 : 1;\n  if (rtl && (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\"))\n    direction *= -1;\n  const next =\n    event.key === \"Home\"\n      ? 0\n      : event.key === \"End\"\n        ? buttons.length - 1\n        : index < 0\n          ? direction > 0\n            ? 0\n            : buttons.length - 1\n          : Math.max(0, Math.min(buttons.length - 1, index + direction));\n  event.preventDefault();\n  const button = buttons[next];\n  button.focus({ preventScroll: true });\n  if (event.currentTarget.dataset.presentation === \"flow\") {\n    const feed = button.closest<HTMLElement>(\n      '[data-slot=\"timeline-feed-viewport\"]',\n    );\n    if (feed) {\n      const view = feed.getBoundingClientRect();\n      const rect = button.getBoundingClientRect();\n      if (rect.top < view.top) feed.scrollTop += rect.top - view.top;\n      else if (rect.bottom > view.bottom)\n        feed.scrollTop += rect.bottom - view.bottom;\n    } else\n      button.scrollIntoView?.({\n        block: \"nearest\",\n        inline: \"nearest\",\n        behavior: \"instant\",\n      });\n  }\n  onNavigate?.(button.dataset.timelineId ?? null);\n  return true;\n}\n\nfunction getCardPositionClasses({\n  index,\n  orientation,\n  layout,\n}: {\n  index: number;\n  orientation: TimelineOrientation;\n  layout: TimelineLayout;\n}) {\n  if (layout === \"story\") {\n    return \"\";\n  }\n\n  const alternatingBefore = index % 2 === 0;\n\n  if (orientation === \"horizontal\") {\n    if (layout === \"alternating\" && alternatingBefore) {\n      return \"bottom-16 left-1/2 -translate-x-1/2\";\n    }\n\n    return \"left-1/2 top-14 -translate-x-1/2\";\n  }\n\n  if (layout === \"alternating\" && alternatingBefore) {\n    return \"right-16 top-1/2 -translate-y-1/2\";\n  }\n\n  return \"left-14 top-1/2 -translate-y-1/2\";\n}\n\nfunction renderItemDate(\n  item: NormalizedTimelineItem,\n  layout: TimelineLayout = \"rail\",\n) {\n  const label = getTimelineDateLabel(item);\n\n  if (!label) {\n    return null;\n  }\n\n  const className =\n    layout === \"story\"\n      ? \"block font-mono text-xl font-semibold leading-none tracking-[0.08em] text-primary\"\n      : \"text-xs font-medium text-muted-foreground\";\n\n  if (item.datetimeAttribute) {\n    return (\n      <time dateTime={item.datetimeAttribute} className={className}>\n        {label}\n      </time>\n    );\n  }\n\n  return <span className={className}>{label}</span>;\n}\n\nfunction renderItemDescription(\n  description: ReactNode,\n  layout: TimelineLayout = \"rail\",\n) {\n  if (description === undefined || description === null) {\n    return null;\n  }\n\n  const className =\n    layout === \"story\"\n      ? \"text-lg leading-8 text-muted-foreground sm:text-xl sm:leading-9\"\n      : \"text-sm leading-6 text-muted-foreground\";\n\n  if (typeof description === \"string\" || typeof description === \"number\") {\n    return <p className={className}>{description}</p>;\n  }\n\n  return <div className={className}>{description}</div>;\n}\n\nfunction renderDefaultItemContent(\n  item: NormalizedTimelineItem,\n  titleId: string,\n  layout: TimelineLayout,\n) {\n  const itemTitle = item.title ?? item.id;\n\n  if (layout === \"story\") {\n    return (\n      <div className=\"space-y-[var(--dt-space-4)]\">\n        {renderItemDate(item, layout)}\n        <div className=\"space-y-[var(--dt-space-4)]\">\n          <h3\n            id={titleId}\n            className=\"font-heading text-foreground text-2xl leading-tight font-semibold tracking-tight sm:text-3xl\"\n          >\n            {itemTitle}\n          </h3>\n          {item.image ? (\n            <img\n              src={item.image.src}\n              alt={item.image.alt}\n              width={item.image.width}\n              height={item.image.height}\n              loading=\"lazy\"\n              className=\"border-timeline-border aspect-video w-full rounded-sm border object-cover\"\n            />\n          ) : null}\n          {renderItemDescription(item.description, layout)}\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-[var(--dt-space-3)]\">\n      <div className=\"space-y-[var(--dt-space-1)]\">\n        {renderItemDate(item, layout)}\n        <h3\n          id={titleId}\n          className=\"text-foreground text-sm leading-6 font-semibold\"\n        >\n          {itemTitle}\n        </h3>\n      </div>\n      {item.image ? (\n        <img\n          src={item.image.src}\n          alt={item.image.alt}\n          width={item.image.width}\n          height={item.image.height}\n          loading=\"lazy\"\n          className=\"border-timeline-border aspect-video w-full rounded-sm border object-cover\"\n        />\n      ) : null}\n      {renderItemDescription(item.description, layout)}\n    </div>\n  );\n}\n\nfunction ZoomOutIcon() {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className=\"size-4\"\n      fill=\"none\"\n      viewBox=\"0 0 24 24\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n    >\n      <path d=\"M5 12h14\" strokeLinecap=\"round\" />\n    </svg>\n  );\n}\n\nfunction ZoomInIcon() {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className=\"size-4\"\n      fill=\"none\"\n      viewBox=\"0 0 24 24\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n    >\n      <path d=\"M12 5v14M5 12h14\" strokeLinecap=\"round\" />\n    </svg>\n  );\n}\n\nfunction FitViewIcon() {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className=\"size-4\"\n      fill=\"none\"\n      viewBox=\"0 0 24 24\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n    >\n      <path\n        d=\"M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path d=\"M9 12h6\" strokeLinecap=\"round\" />\n    </svg>\n  );\n}\n\nfunction ResetViewIcon() {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className=\"size-4\"\n      fill=\"none\"\n      viewBox=\"0 0 24 24\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n    >\n      <path\n        d=\"M4 7v5h5M20 17v-5h-5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path\n        d=\"M7 12a5 5 0 0 1 8.5-3.5L20 12M17 12a5 5 0 0 1-8.5 3.5L4 12\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction TimelineRail({\n  items,\n  orientation,\n}: {\n  items: NormalizedTimelineItem[];\n  orientation: TimelineOrientation;\n}) {\n  if (items.length === 0) {\n    return null;\n  }\n\n  const minX = Math.min(...items.map((item) => item.point.x));\n  const maxX = Math.max(...items.map((item) => item.point.x));\n  const minY = Math.min(...items.map((item) => item.point.y));\n  const maxY = Math.max(...items.map((item) => item.point.y));\n\n  if (orientation === \"horizontal\") {\n    return (\n      <span\n        aria-hidden=\"true\"\n        data-slot=\"timeline-rail\"\n        className={cn(timelineRailClasses, \"h-px\")}\n        style={{\n          left: minX,\n          top: items[0]?.point.y ?? timelineGeometry.horizontalRailY,\n          width: Math.max(0, maxX - minX),\n        }}\n      />\n    );\n  }\n\n  return (\n    <span\n      aria-hidden=\"true\"\n      data-slot=\"timeline-rail\"\n      className={cn(timelineRailClasses, \"w-px\")}\n      style={{\n        left: items[0]?.point.x ?? timelineGeometry.verticalRailX,\n        top: minY,\n        height: Math.max(0, maxY - minY),\n      }}\n    />\n  );\n}\n\nfunction TimelineItemInner<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n>(\n  {\n    className,\n    item,\n    mode,\n    orientation = \"horizontal\",\n    layout = \"rail\",\n    presentation = \"canvas\",\n    interactive = true,\n    selected = false,\n    renderItem,\n    onSelect,\n    variant = \"activity\",\n    details,\n    expanded = false,\n    onExpandedChange,\n    style,\n    ...props\n  }: TimelineItemProps<TPayload>,\n  ref: ForwardedRef<HTMLLIElement>,\n) {\n  const cardId = useId();\n  const titleId = `${cardId}-title`;\n  const isInteractive = interactive && !item.disabled;\n  const handleSelect = () => {\n    if (isInteractive) {\n      onSelect?.(item.id);\n    }\n  };\n  const detailsRef = useRef<HTMLDivElement>(null);\n  const disclosureRef = useRef<HTMLButtonElement>(null);\n  useEffect(() => {\n    if (!expanded && detailsRef.current?.contains(document.activeElement))\n      disclosureRef.current?.focus();\n  }, [expanded]);\n  // A \"flow\" presentation renders in normal document flow (marker column + rail\n  // + card). \"Story\" is the editorial typography variant of that flow. Compact\n  // flow reuses the flow structure with the default compact card styling.\n  const isFlow = presentation === \"flow\";\n  const isStory = mode === \"story\" || layout === \"story\";\n  const isAlternatingFlow = isFlow && layout === \"alternating\";\n  const alternatingSide = item.index % 2 === 0 ? \"end\" : \"start\";\n  const contentLayout = isStory ? \"story\" : layout;\n  const cardContent = renderItem\n    ? renderItem(item)\n    : renderDefaultItemContent(item, titleId, contentLayout);\n  const StatusIcon = timelineStatusIcons[item.status];\n  const hasDetails =\n    isFlow && details !== undefined && details !== null && details !== false;\n  const itemStyle = isFlow\n    ? style\n    : { left: item.point.x, top: item.point.y, ...style };\n\n  return (\n    <li\n      {...props}\n      ref={ref}\n      data-slot=\"timeline-item\"\n      data-timeline-id={item.id}\n      data-status={item.status}\n      data-selected={selected ? \"true\" : undefined}\n      data-disabled={item.disabled ? \"true\" : undefined}\n      className={cn(\n        isFlow\n          ? isStory\n            ? cn(\n                timelineStoryItemClasses,\n                timelineStoryItemLayoutClasses[\n                  isAlternatingFlow ? \"alternating\" : \"default\"\n                ],\n              )\n            : cn(\n                timelineFlowItemClasses,\n                timelineFlowItemLayoutClasses[\n                  isAlternatingFlow ? \"alternating\" : \"default\"\n                ],\n              )\n          : timelineItemClasses,\n        className,\n      )}\n      style={itemStyle}\n    >\n      <span\n        aria-hidden=\"true\"\n        data-slot=\"timeline-marker\"\n        data-selected={selected ? \"true\" : undefined}\n        className={cn(\n          timelineMarkerBaseClasses,\n          isFlow\n            ? isStory\n              ? timelineStoryMarkerClasses\n              : timelineFlowMarkerClasses\n            : timelineViewportMarkerClasses,\n          isAlternatingFlow &&\n            (isStory\n              ? timelineStoryMarkerAlternatingClasses\n              : timelineFlowMarkerAlternatingClasses),\n          isStory\n            ? timelineStoryMarkerStatusClasses[item.status]\n            : timelineMarkerStatusClasses[item.status],\n        )}\n      >\n        {item.marker ?? (\n          <StatusIcon\n            className={isStory ? \"size-3.5\" : \"size-4\"}\n            strokeWidth={2}\n          />\n        )}\n      </span>\n      <article\n        aria-current={item.status === \"current\" ? \"step\" : undefined}\n        aria-labelledby={renderItem ? undefined : titleId}\n        data-slot=\"timeline-card\"\n        data-status={item.status}\n        data-selected={selected ? \"true\" : undefined}\n        data-disabled={item.disabled ? \"true\" : undefined}\n        data-interactive={isInteractive ? \"true\" : undefined}\n        className={cn(\n          timelineCardBaseClasses,\n          isFlow\n            ? isStory\n              ? timelineStoryCardClasses\n              : timelineFlowCardClasses\n            : timelineViewportCardClasses,\n          isFlow &&\n            !isStory &&\n            variant === \"activity\" &&\n            \"border-timeline-border data-[selected=true]:bg-muted/40 rounded-none border-0 border-b bg-transparent px-0 pt-0 pb-[var(--dt-density-gap)] shadow-none hover:shadow-none data-[selected=true]:rounded-md data-[selected=true]:px-3 data-[selected=true]:shadow-none\",\n          isAlternatingFlow &&\n            (isStory\n              ? timelineStoryCardAlternatingClasses\n              : timelineFlowCardAlternatingClasses)[alternatingSide],\n          !isFlow &&\n            getCardPositionClasses({ index: item.index, orientation, layout }),\n        )}\n      >\n        <div\n          className={cn(\n            \"mb-2 flex items-center justify-between gap-3 text-xs\",\n            isStory && !isInteractive && \"sr-only\",\n          )}\n        >\n          <span className=\"text-muted-foreground\">\n            Status: {timelineStatusLabel[item.status]}\n          </span>\n          {isInteractive ? (\n            <button\n              type=\"button\"\n              data-slot=\"timeline-card-action\"\n              data-timeline-id={item.id}\n              aria-label={\n                renderItem\n                  ? typeof item.title === \"string\"\n                    ? item.title\n                    : item.id\n                  : undefined\n              }\n              aria-labelledby={renderItem ? undefined : titleId}\n              aria-current={item.status === \"current\" ? \"step\" : undefined}\n              aria-pressed={selected}\n              className=\"text-primary hover:bg-muted focus-visible:ring-ring shrink-0 rounded-sm px-2 py-1 focus-visible:ring-2 focus-visible:outline-none\"\n              onClick={handleSelect}\n            >\n              {selected ? \"Selected\" : \"Select\"}\n            </button>\n          ) : null}\n        </div>\n        <div\n          data-slot=\"timeline-card-content\"\n          aria-disabled={item.disabled || undefined}\n        >\n          {cardContent}\n        </div>\n        {hasDetails ? (\n          <>\n            <button\n              ref={disclosureRef}\n              type=\"button\"\n              data-slot=\"timeline-disclosure\"\n              aria-expanded={expanded}\n              aria-controls={`${cardId}-details`}\n              aria-label={`${expanded ? \"Hide\" : \"Show\"} details for ${typeof item.title === \"string\" ? item.title : item.id}`}\n              disabled={item.disabled}\n              onClick={onExpandedChange}\n              className=\"text-primary focus-visible:ring-ring mt-3 inline-flex items-center gap-2 rounded-sm py-1 text-sm font-medium hover:underline focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50\"\n            >\n              {expanded ? \"Hide details\" : \"Show details\"}\n              <span aria-hidden=\"true\">{expanded ? \"−\" : \"+\"}</span>\n            </button>\n            <div\n              id={`${cardId}-details`}\n              ref={detailsRef}\n              data-slot=\"timeline-details\"\n              data-expanded={expanded ? \"true\" : \"false\"}\n              aria-hidden={!expanded}\n              className=\"grid grid-rows-[0fr] data-[expanded=false]:invisible data-[expanded=false]:opacity-0 data-[expanded=true]:grid-rows-[1fr] motion-safe:transition-[grid-template-rows,opacity] motion-safe:duration-[var(--dt-motion-standard)]\"\n            >\n              <div className=\"min-h-0 overflow-hidden\">\n                <div className=\"text-muted-foreground pt-3 text-sm\">\n                  {details}\n                </div>\n              </div>\n            </div>\n          </>\n        ) : null}\n      </article>\n    </li>\n  );\n}\n\nexport const TimelineItem = forwardRef(TimelineItemInner) as (<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n>(\n  props: TimelineItemProps<TPayload> & RefAttributes<HTMLLIElement>,\n) => ReactElement | null) & { displayName?: string };\n\nTimelineItem.displayName = \"TimelineItem\";\n\nexport function TimelineControls({\n  className,\n  zoom,\n  minZoom,\n  maxZoom,\n  visibility = \"hover\",\n  visible = false,\n  onZoomIn,\n  onZoomOut,\n  onReset,\n  onFit,\n  ...props\n}: TimelineControlsProps) {\n  return (\n    <div\n      {...props}\n      data-slot=\"timeline-controls\"\n      data-visibility={visibility}\n      data-visible={visibility === \"always\" || visible ? \"true\" : \"false\"}\n      className={cn(\n        \"border-timeline-border bg-background/95 absolute top-[var(--dt-space-3)] right-[var(--dt-space-3)] z-20 flex items-center gap-[var(--dt-space-1)] rounded-md border p-[var(--dt-space-1)] shadow-md backdrop-blur motion-safe:transition-[opacity,transform] motion-safe:duration-200 motion-safe:ease-out motion-reduce:transition-none\",\n        timelineControlsVisibilityClasses[visibility],\n        className,\n      )}\n    >\n      <button\n        type=\"button\"\n        aria-label=\"Zoom out\"\n        title=\"Zoom out\"\n        className={timelineControlButtonClasses}\n        disabled={zoom <= minZoom}\n        onClick={onZoomOut}\n      >\n        <ZoomOutIcon />\n      </button>\n      <button\n        type=\"button\"\n        aria-label=\"Zoom in\"\n        title=\"Zoom in\"\n        className={timelineControlButtonClasses}\n        disabled={zoom >= maxZoom}\n        onClick={onZoomIn}\n      >\n        <ZoomInIcon />\n      </button>\n      <button\n        type=\"button\"\n        aria-label=\"Fit timeline\"\n        title=\"Fit timeline\"\n        className={timelineControlButtonClasses}\n        onClick={onFit}\n      >\n        <FitViewIcon />\n      </button>\n      <button\n        type=\"button\"\n        aria-label=\"Reset timeline view\"\n        title=\"Reset timeline view\"\n        className={timelineControlButtonClasses}\n        onClick={onReset}\n      >\n        <ResetViewIcon />\n      </button>\n    </div>\n  );\n}\n\nexport const TimelineViewport = forwardRef<\n  HTMLDivElement,\n  TimelineViewportProps\n>(\n  (\n    {\n      children,\n      className,\n      contentSize,\n      interactive = true,\n      orientation = \"horizontal\",\n      layout = \"rail\",\n      presentation = \"canvas\",\n      viewport,\n      selectedPoint,\n      onNavigate,\n      getPreviousId: _getPreviousId,\n      getNextId: _getNextId,\n      getFirstId: _getFirstId,\n      getLastId: _getLastId,\n      onKeyDown,\n      onPointerDown,\n      onPointerMove,\n      onPointerUp,\n      onPointerCancel,\n      onMouseEnter,\n      onMouseLeave,\n      onFocus,\n      onBlur,\n      onWheel,\n      ...props\n    },\n    ref,\n  ) => {\n    const viewportRef = useRef<HTMLDivElement | null>(null);\n    const contentRef = useRef<HTMLDivElement | null>(null);\n    const options = useMemo(\n      () => normalizeViewportOptions(viewport),\n      [viewport],\n    );\n    const [transform, setTransform] = useState<TimelineTransform>(() =>\n      getDefaultTimelineTransform(viewport),\n    );\n    const [dragging, setDragging] = useState(false);\n    const [controlsVisible, setControlsVisible] = useState(false);\n    const suppressClickRef = useRef(false);\n    const initializedRef = useRef(false);\n    const dragRef = useRef<{\n      pointerId: number;\n      startX: number;\n      startY: number;\n      transform: TimelineTransform;\n      moved: boolean;\n    } | null>(null);\n    const mergedRef = useCallback(\n      (node: HTMLDivElement | null) => {\n        viewportRef.current = node;\n\n        if (typeof ref === \"function\") {\n          ref(node);\n        } else if (ref) {\n          ref.current = node;\n        }\n      },\n      [ref],\n    );\n    const getViewportSize = useCallback(() => {\n      const rect = viewportRef.current?.getBoundingClientRect();\n\n      return {\n        width: rect?.width ?? 0,\n        height: rect?.height ?? 0,\n      };\n    }, []);\n    const zoomBy = useCallback(\n      (delta: number, origin?: TimelinePoint) => {\n        setTransform((current) =>\n          zoomTimelineTransform({\n            transform: current,\n            delta,\n            minZoom: options.minZoom,\n            maxZoom: options.maxZoom,\n            origin,\n          }),\n        );\n      },\n      [options.maxZoom, options.minZoom],\n    );\n    const revealCard = useCallback(\n      (card?: HTMLElement | null, initial = false) => {\n        const node = viewportRef.current;\n        const target =\n          card ??\n          node?.querySelector<HTMLElement>(\n            '[data-slot=\"timeline-card\"][data-selected=\"true\"]',\n          ) ??\n          node?.querySelector<HTMLElement>('[data-slot=\"timeline-card\"]');\n        if (!node || !target) return;\n        const view = node.getBoundingClientRect();\n        const rect = target.getBoundingClientRect();\n        if (!view.width || !rect.width) return;\n        const content = contentRef.current;\n        if (!content) return;\n        const origin = content.getBoundingClientRect();\n        const matrix = new DOMMatrixReadOnly(\n          getComputedStyle(content).transform,\n        );\n        const zoom = matrix.a || 1;\n        const local = {\n          left: (rect.left - origin.left) / zoom,\n          top: (rect.top - origin.top) / zoom,\n          width: rect.width / zoom,\n          height: rect.height / zoom,\n        };\n        setTransform((current) => {\n          const left = current.x + local.left * current.zoom;\n          const top = current.y + local.top * current.zoom;\n          const right = left + local.width * current.zoom;\n          const bottom = top + local.height * current.zoom;\n          const dx =\n            initial || left < 20\n              ? 20 - left\n              : right > view.width - 20\n                ? view.width - 20 - right\n                : 0;\n          const dy =\n            initial || top < 72\n              ? 72 - top\n              : bottom > view.height - 20\n                ? view.height - 20 - bottom\n                : 0;\n          return dx || dy\n            ? { ...current, x: current.x + dx, y: current.y + dy }\n            : current;\n        });\n      },\n      [],\n    );\n    const reset = useCallback(() => {\n      setTransform(getDefaultTimelineTransform(viewport));\n      initializedRef.current = false;\n      requestAnimationFrame(() => revealCard(undefined, true));\n    }, [viewport, revealCard]);\n    const fit = useCallback(() => {\n      const node = contentRef.current;\n      const view = getViewportSize();\n      const cards = Array.from(\n        node?.querySelectorAll<HTMLElement>('[data-slot=\"timeline-card\"]') ??\n          [],\n      );\n      if (!node || !cards.length || !view.width) {\n        setTransform(\n          fitTimelineTransform({\n            contentSize,\n            viewportSize: view,\n            minZoom: options.minZoom,\n            maxZoom: options.maxZoom,\n          }),\n        );\n        return;\n      }\n      const origin = node.getBoundingClientRect();\n      const renderedZoom =\n        new DOMMatrixReadOnly(getComputedStyle(node).transform).a || 1;\n      const rects = cards.map((card) => card.getBoundingClientRect());\n      const minX =\n        (Math.min(...rects.map((rect) => rect.left)) - origin.left) /\n          renderedZoom -\n        20;\n      const minY =\n        (Math.min(...rects.map((rect) => rect.top)) - origin.top) /\n          renderedZoom -\n        64;\n      const width =\n        (Math.max(...rects.map((rect) => rect.right)) - origin.left) /\n          renderedZoom -\n        minX +\n        20;\n      const height =\n        (Math.max(...rects.map((rect) => rect.bottom)) - origin.top) /\n          renderedZoom -\n        minY +\n        20;\n      const fitted = fitTimelineTransform({\n        contentSize: { width, height },\n        viewportSize: {\n          width: view.width,\n          height: Math.max(1, view.height - 48),\n        },\n        minZoom: options.minZoom,\n        maxZoom: options.maxZoom,\n      });\n      setTransform({\n        ...fitted,\n        x: fitted.x - minX * fitted.zoom,\n        y: fitted.y + 48 - minY * fitted.zoom,\n      });\n    }, [contentSize, getViewportSize, options.maxZoom, options.minZoom]);\n\n    useEffect(() => {\n      const node = viewportRef.current;\n      if (!node) return;\n      let frame = 0;\n      const measure = () => {\n        const width = node.clientWidth;\n        if (!width) return;\n        node.style.setProperty(\n          \"--timeline-card-width\",\n          `${Math.max(80, Math.min(320, width - 40))}px`,\n        );\n        const cards = Array.from(\n          node.querySelectorAll<HTMLElement>('[data-slot=\"timeline-card\"]'),\n        );\n        const height = Math.max(0, ...cards.map((card) => card.offsetHeight));\n        node.style.minHeight = `${Math.max(352, height + 112)}px`;\n        cancelAnimationFrame(frame);\n        frame = requestAnimationFrame(() => {\n          revealCard(undefined, !initializedRef.current);\n          initializedRef.current = true;\n        });\n      };\n      measure();\n      const observer =\n        typeof ResizeObserver === \"undefined\"\n          ? undefined\n          : new ResizeObserver(measure);\n      observer?.observe(node);\n      node\n        .querySelectorAll('[data-slot=\"timeline-card\"]')\n        .forEach((card) => observer?.observe(card));\n      return () => {\n        cancelAnimationFrame(frame);\n        observer?.disconnect();\n      };\n    }, [revealCard, contentSize]);\n    useEffect(() => {\n      const frame = requestAnimationFrame(() =>\n        revealCard(undefined, !initializedRef.current),\n      );\n      return () => cancelAnimationFrame(frame);\n    }, [selectedPoint?.x, selectedPoint?.y, revealCard, options.defaultZoom]);\n\n    const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n      onKeyDown?.(event);\n\n      if (event.defaultPrevented || !interactive) {\n        return;\n      }\n\n      if (navigateTimeline(event, onNavigate)) return;\n      if (\n        event.target !== event.currentTarget &&\n        (event.target as HTMLElement).dataset.slot !== \"timeline-card-action\"\n      )\n        return;\n      if (event.key === \"+\" || event.key === \"=\") {\n        event.preventDefault();\n        zoomBy(0.2);\n      } else if (event.key === \"-\") {\n        event.preventDefault();\n        zoomBy(-0.2);\n      } else if (event.key === \"0\") {\n        event.preventDefault();\n        reset();\n      } else if (event.key.toLowerCase() === \"f\") {\n        event.preventDefault();\n        fit();\n      }\n    };\n\n    const handlePointerDown = (event: PointerEvent<HTMLDivElement>) => {\n      onPointerDown?.(event);\n\n      if (\n        event.defaultPrevented ||\n        !interactive ||\n        event.button !== 0 ||\n        (event.target instanceof HTMLElement &&\n          event.target.closest(\n            '[data-slot=\"timeline-card\"], [data-slot=\"timeline-controls\"], button, a, input, textarea, select, [contenteditable=\"true\"]',\n          ))\n      ) {\n        return;\n      }\n\n      suppressClickRef.current = false;\n      dragRef.current = {\n        pointerId: event.pointerId,\n        startX: event.clientX,\n        startY: event.clientY,\n        transform,\n        moved: false,\n      };\n    };\n\n    const handlePointerMove = (event: PointerEvent<HTMLDivElement>) => {\n      onPointerMove?.(event);\n      const drag = dragRef.current;\n\n      if (!drag || drag.pointerId !== event.pointerId) {\n        return;\n      }\n\n      if (\n        !drag.moved &&\n        Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < 6\n      )\n        return;\n      if (!drag.moved) {\n        drag.moved = true;\n        suppressClickRef.current = true;\n        setDragging(true);\n        event.currentTarget.focus({ preventScroll: true });\n        event.currentTarget.setPointerCapture?.(event.pointerId);\n      }\n      setTransform({\n        ...drag.transform,\n        x: drag.transform.x + event.clientX - drag.startX,\n        y: drag.transform.y + event.clientY - drag.startY,\n      });\n    };\n\n    const endDrag = (event: PointerEvent<HTMLDivElement>) => {\n      dragRef.current = null;\n      setDragging(false);\n      if (event.currentTarget.hasPointerCapture?.(event.pointerId))\n        event.currentTarget.releasePointerCapture?.(event.pointerId);\n    };\n\n    const handlePointerUp = (event: PointerEvent<HTMLDivElement>) => {\n      onPointerUp?.(event);\n      endDrag(event);\n    };\n\n    const handlePointerCancel = (event: PointerEvent<HTMLDivElement>) => {\n      onPointerCancel?.(event);\n      endDrag(event);\n    };\n\n    const showHoverControls = () => {\n      if (options.controlsVisibility === \"hover\") {\n        setControlsVisible(true);\n      }\n    };\n\n    const hideHoverControls = () => {\n      if (options.controlsVisibility === \"hover\") {\n        setControlsVisible(false);\n      }\n    };\n\n    const handleMouseEnter = (event: MouseEvent<HTMLDivElement>) => {\n      onMouseEnter?.(event);\n\n      if (!event.defaultPrevented) {\n        showHoverControls();\n      }\n    };\n\n    const handleMouseLeave = (event: MouseEvent<HTMLDivElement>) => {\n      onMouseLeave?.(event);\n\n      if (\n        !event.defaultPrevented &&\n        !event.currentTarget.contains(document.activeElement)\n      ) {\n        hideHoverControls();\n      }\n    };\n\n    const handleFocus = (event: FocusEvent<HTMLDivElement>) => {\n      onFocus?.(event);\n\n      if (!event.defaultPrevented) {\n        showHoverControls();\n        const target = event.target as HTMLElement;\n        if (target.dataset.slot === \"timeline-card-action\")\n          revealCard(\n            target.closest<HTMLElement>('[data-slot=\"timeline-card\"]'),\n          );\n      }\n    };\n\n    const handleBlur = (event: FocusEvent<HTMLDivElement>) => {\n      onBlur?.(event);\n\n      if (\n        event.defaultPrevented ||\n        (event.relatedTarget instanceof Node &&\n          event.currentTarget.contains(event.relatedTarget))\n      ) {\n        return;\n      }\n\n      hideHoverControls();\n    };\n\n    const handleWheel = (event: WheelEvent<HTMLDivElement>) => {\n      onWheel?.(event);\n\n      if (\n        event.defaultPrevented ||\n        !interactive ||\n        options.wheelZoom === false\n      ) {\n        return;\n      }\n\n      const hasModifier = event.ctrlKey || event.metaKey || event.altKey;\n\n      if (options.wheelZoom === \"modifier\" && !hasModifier) {\n        return;\n      }\n\n      event.preventDefault();\n\n      const rect = event.currentTarget.getBoundingClientRect();\n\n      zoomBy(event.deltaY < 0 ? 0.15 : -0.15, {\n        x: event.clientX - rect.left,\n        y: event.clientY - rect.top,\n      });\n    };\n    const contentStyle: TimelineContentStyle = {\n      width: contentSize.width,\n      height: contentSize.height,\n      \"--timeline-transform\": `translate3d(${transform.x}px, ${transform.y}px, 0) scale(${transform.zoom})`,\n      transform: \"var(--timeline-transform)\",\n    };\n\n    return (\n      /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex -- This focusable viewport supports keyboard navigation and pointer gestures. */\n      <div\n        {...props}\n        ref={mergedRef}\n        role=\"region\"\n        aria-label={props[\"aria-label\"] ?? \"Timeline viewport\"}\n        tabIndex={interactive ? 0 : undefined}\n        data-slot=\"timeline-viewport\"\n        data-interactive={interactive ? \"true\" : \"false\"}\n        data-dragging={dragging ? \"true\" : undefined}\n        data-orientation={orientation}\n        data-layout={layout}\n        data-presentation={presentation}\n        data-chrome={options.chrome}\n        className={cn(\n          timelineViewportClasses,\n          timelineViewportChromeClasses[options.chrome],\n          className,\n        )}\n        onKeyDown={handleKeyDown}\n        onClickCapture={(event) => {\n          if (suppressClickRef.current) {\n            event.preventDefault();\n            event.stopPropagation();\n            suppressClickRef.current = false;\n          }\n        }}\n        onPointerDown={handlePointerDown}\n        onPointerMove={handlePointerMove}\n        onPointerUp={handlePointerUp}\n        onPointerCancel={handlePointerCancel}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        onFocus={handleFocus}\n        onBlur={handleBlur}\n        onWheel={handleWheel}\n      >\n        {/* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */}\n        {interactive ? (\n          <p className=\"text-muted-foreground pointer-events-none absolute inset-x-3 bottom-2 z-10 text-center text-xs\">\n            Drag the track · Arrow keys to navigate\n          </p>\n        ) : null}\n        {interactive && options.controls ? (\n          <TimelineControls\n            zoom={transform.zoom}\n            minZoom={options.minZoom}\n            maxZoom={options.maxZoom}\n            visibility={options.controlsVisibility}\n            visible={controlsVisible}\n            onZoomIn={() => zoomBy(0.2)}\n            onZoomOut={() => zoomBy(-0.2)}\n            onFit={fit}\n            onReset={reset}\n          />\n        ) : null}\n        <div\n          ref={contentRef}\n          data-slot=\"timeline-viewport-content\"\n          data-dragging={dragging ? \"true\" : undefined}\n          className={timelineViewportContentClasses}\n          style={contentStyle}\n        >\n          {children}\n        </div>\n      </div>\n    );\n  },\n);\n\nTimelineViewport.displayName = \"TimelineViewport\";\n\ninterface TimelineFlowViewportProps extends Omit<\n  TimelineViewportProps,\n  \"contentSize\" | \"selectedPoint\"\n> {}\n\nfunction TimelineFlowViewport({\n  children,\n  className,\n  interactive = false,\n  orientation = \"vertical\",\n  layout = \"story\",\n  presentation = \"flow\",\n  viewport,\n  onNavigate,\n  getPreviousId: _getPreviousId,\n  getNextId: _getNextId,\n  getFirstId: _getFirstId,\n  getLastId: _getLastId,\n  onKeyDown,\n  ...props\n}: TimelineFlowViewportProps) {\n  const options = normalizeViewportOptions(viewport);\n  const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n    onKeyDown?.(event);\n\n    if (event.defaultPrevented || !interactive) {\n      return;\n    }\n\n    navigateTimeline(event, onNavigate);\n  };\n\n  return (\n    /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex -- This focusable viewport supports keyboard navigation and pointer gestures. */\n    <div\n      {...props}\n      role=\"region\"\n      aria-label={props[\"aria-label\"] ?? \"Timeline viewport\"}\n      tabIndex={interactive ? 0 : undefined}\n      data-slot=\"timeline-viewport\"\n      data-interactive={interactive ? \"true\" : \"false\"}\n      data-orientation={orientation}\n      data-layout={layout}\n      data-presentation={presentation}\n      data-chrome={options.chrome}\n      className={cn(\n        timelineStoryViewportClasses,\n        timelineViewportChromeClasses[options.chrome],\n        className,\n      )}\n      onKeyDown={handleKeyDown}\n    >\n      {/* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */}\n      {children}\n    </div>\n  );\n}\n\ntype TimelineRevealItemStyle = CSSProperties & {\n  \"--timeline-reveal-index\"?: number;\n  \"--timeline-reveal-interval\"?: string;\n  \"--timeline-reveal-duration\"?: string;\n  \"--timeline-reveal-initial-delay\"?: string;\n  \"--timeline-reveal-delay\"?: string;\n  \"--timeline-rail-duration\"?: string;\n  \"--timeline-rail-delay\"?: string;\n};\n\ntype TimelineRevealItemProps = {\n  onFocusCapture?: () => void;\n  ref?: (node: HTMLLIElement | null) => void;\n  style?: TimelineRevealItemStyle;\n  \"data-reveal\"?: TimelineRevealMode;\n  \"data-revealed\"?: \"true\" | \"false\";\n  \"data-reveal-last\"?: \"true\";\n  \"data-timeline-reveal-id\"?: string;\n};\n\ntype UseTimelineRevealResult = {\n  getItemRevealProps: (id: string, isLast: boolean) => TimelineRevealItemProps;\n  handleAnimationEnd: (event: AnimationEvent<HTMLElement>) => void;\n};\n\nconst REVEAL_COMPLETE_BUFFER_MS = 60;\nconst subscribeReducedMotion = (notify: () => void) => {\n  const query = window.matchMedia?.(\"(prefers-reduced-motion: reduce)\");\n  query?.addEventListener(\"change\", notify);\n  return () => query?.removeEventListener(\"change\", notify);\n};\nconst getReducedMotion = () =>\n  window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches ?? false;\nconst getServerReducedMotion = () => false;\n\n// Reveal progression is driven by React state (a ref-backed revealed set), not\n// by animation events, so it stays correct under `prefers-reduced-motion` and\n// in SSR. CSS only turns that state into motion.\nfunction useTimelineReveal({\n  itemIds,\n  reveal,\n  active,\n  options,\n  revealCount,\n  onItemReveal,\n  onRevealComplete,\n}: {\n  itemIds: string[];\n  reveal: TimelineRevealMode;\n  active: boolean;\n  options: NormalizedTimelineRevealOptions;\n  revealCount: number | undefined;\n  onItemReveal?: (id: string, index: number) => void;\n  onRevealComplete?: () => void;\n}): UseTimelineRevealResult {\n  const reduced = useSyncExternalStore(\n    subscribeReducedMotion,\n    getReducedMotion,\n    getServerReducedMotion,\n  );\n  const focusedRef = useRef<Set<string>>(new Set());\n  const revealedRef = useRef<Set<string>>(new Set());\n  const orderRef = useRef<Map<string, number>>(new Map());\n  const inViewRef = useRef<Set<string>>(new Set());\n  const nodesRef = useRef<Map<string, HTMLLIElement>>(new Map());\n  const observerRef = useRef<IntersectionObserver | null>(null);\n  const completeFiredRef = useRef(false);\n  const completionDeadlineRef = useRef(0);\n  const completeTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(\n    undefined,\n  );\n  const [, setRevealTick] = useState(0);\n  const forceRender = useCallback(() => setRevealTick((tick) => tick + 1), []);\n\n  const stateRef = useRef({\n    itemIds,\n    reveal,\n    active,\n    options,\n    reduced,\n    trigger: options.trigger,\n    revealCount,\n    onItemReveal,\n    onRevealComplete,\n  });\n  // Mirror the latest props into a ref so the stable reconcile/observer\n  // callbacks always read current values without re-subscribing.\n  // eslint-disable-next-line react-hooks/refs\n  stateRef.current = {\n    itemIds,\n    reveal,\n    active,\n    options,\n    reduced,\n    trigger: options.trigger,\n    revealCount,\n    onItemReveal,\n    onRevealComplete,\n  };\n\n  const reconcile = useCallback(() => {\n    const state = stateRef.current;\n\n    if (!state.active) {\n      return;\n    }\n\n    const total = state.itemIds.length;\n    const isTarget = (id: string, index: number) => {\n      if (state.reduced || focusedRef.current.has(id)) return true;\n      if (state.trigger === \"manual\") {\n        return index < clampTimelineRevealCount(state.revealCount, total);\n      }\n\n      if (state.trigger === \"in-view\") {\n        return inViewRef.current.has(id);\n      }\n\n      return true;\n    };\n    const newlyRevealed = state.itemIds.filter(\n      (id, index) => isTarget(id, index) && !revealedRef.current.has(id),\n    );\n\n    if (newlyRevealed.length === 0) {\n      return;\n    }\n\n    completeFiredRef.current = false;\n    newlyRevealed.forEach((id, batchOrder) => {\n      orderRef.current.set(id, state.reveal === \"stagger\" ? batchOrder : 0);\n      revealedRef.current.add(id);\n\n      const node = nodesRef.current.get(id);\n      if (node && observerRef.current) {\n        observerRef.current.unobserve(node);\n      }\n    });\n    forceRender();\n\n    newlyRevealed.forEach((id) => {\n      state.onItemReveal?.(id, state.itemIds.indexOf(id));\n    });\n\n    const duration = state.reduced\n      ? 0\n      : getTimelineRevealBatchDuration({\n          batchSize: newlyRevealed.length,\n          reveal: state.reveal,\n          options: state.options,\n        });\n    completionDeadlineRef.current = state.reduced\n      ? Date.now()\n      : Math.max(completionDeadlineRef.current, Date.now() + duration);\n    const allRevealed = state.itemIds.every((id) =>\n      revealedRef.current.has(id),\n    );\n    if (allRevealed) {\n      clearTimeout(completeTimerRef.current);\n      completeTimerRef.current = setTimeout(\n        () => {\n          const latest = stateRef.current;\n          if (\n            !completeFiredRef.current &&\n            latest.itemIds.every((id) => revealedRef.current.has(id))\n          ) {\n            completeFiredRef.current = true;\n            latest.onRevealComplete?.();\n          }\n        },\n        state.reduced\n          ? 0\n          : Math.max(0, completionDeadlineRef.current - Date.now()) +\n              REVEAL_COMPLETE_BUFFER_MS,\n      );\n    }\n  }, [forceRender]);\n\n  const itemKey = JSON.stringify(itemIds);\n\n  // Prune removed ids so re-added items animate again, then reconcile whenever\n  // the item set or reveal configuration changes.\n  useEffect(() => {\n    if (!active) {\n      return;\n    }\n\n    const idSet = new Set(itemIds);\n    for (const id of Array.from(revealedRef.current)) {\n      if (!idSet.has(id)) revealedRef.current.delete(id);\n    }\n    for (const id of Array.from(orderRef.current.keys())) {\n      if (!idSet.has(id)) orderRef.current.delete(id);\n    }\n    for (const id of Array.from(focusedRef.current)) {\n      if (!idSet.has(id)) focusedRef.current.delete(id);\n    }\n    for (const id of Array.from(inViewRef.current)) {\n      if (!idSet.has(id)) inViewRef.current.delete(id);\n    }\n\n    reconcile();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [\n    active,\n    itemKey,\n    reveal,\n    options.trigger,\n    options.interval,\n    options.duration,\n    options.initialDelay,\n    revealCount,\n    reduced,\n    reconcile,\n  ]);\n\n  // IntersectionObserver for the in-view trigger. A single observer watches the\n  // still-hidden item nodes and reveals them as they scroll into view.\n  useEffect(() => {\n    if (!active || options.trigger !== \"in-view\") {\n      observerRef.current?.disconnect();\n      observerRef.current = null;\n      return;\n    }\n\n    if (typeof IntersectionObserver === \"undefined\") {\n      itemIds.forEach((id) => inViewRef.current.add(id));\n      reconcile();\n      return;\n    }\n\n    const observer = new IntersectionObserver(\n      (entries) => {\n        let changed = false;\n        for (const entry of entries) {\n          if (!entry.isIntersecting) continue;\n          const id = (entry.target as HTMLElement).getAttribute(\n            \"data-timeline-reveal-id\",\n          );\n          if (id) {\n            inViewRef.current.add(id);\n            changed = true;\n          }\n        }\n        if (changed) reconcile();\n      },\n      { rootMargin: \"0px 0px -10% 0px\" },\n    );\n    observerRef.current = observer;\n    nodesRef.current.forEach((node, id) => {\n      if (!revealedRef.current.has(id)) observer.observe(node);\n    });\n\n    return () => {\n      observer.disconnect();\n      observerRef.current = null;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [active, options.trigger, itemKey, reconcile]);\n\n  useEffect(() => () => clearTimeout(completeTimerRef.current), []);\n\n  const registerNode = useCallback((id: string, node: HTMLLIElement | null) => {\n    if (node) {\n      nodesRef.current.set(id, node);\n      if (\n        observerRef.current &&\n        stateRef.current.trigger === \"in-view\" &&\n        !revealedRef.current.has(id)\n      ) {\n        observerRef.current.observe(node);\n      }\n    } else {\n      nodesRef.current.delete(id);\n    }\n  }, []);\n\n  const handleAnimationEnd = useCallback(\n    (event: AnimationEvent<HTMLElement>) => {\n      const state = stateRef.current;\n      if (!state.active || Date.now() < completionDeadlineRef.current) return;\n      if (!String(event.animationName).includes(\"timeline-reveal\")) return;\n      const target = event.target as HTMLElement;\n      if (target.getAttribute(\"data-reveal-last\") !== \"true\") return;\n      if (\n        !completeFiredRef.current &&\n        state.itemIds.every((id) => revealedRef.current.has(id))\n      ) {\n        completeFiredRef.current = true;\n        clearTimeout(completeTimerRef.current);\n        state.onRevealComplete?.();\n      }\n    },\n    [],\n  );\n\n  const getItemRevealProps = (\n    id: string,\n    isLast: boolean,\n  ): TimelineRevealItemProps => {\n    if (!active) {\n      return {};\n    }\n\n    return {\n      onFocusCapture: () => {\n        focusedRef.current.add(id);\n        reconcile();\n      },\n      ref: (node) => registerNode(id, node),\n      \"data-reveal\": reveal,\n      \"data-revealed\": revealedRef.current.has(id) ? \"true\" : \"false\",\n      \"data-reveal-last\": isLast ? \"true\" : undefined,\n      \"data-timeline-reveal-id\": id,\n      style: {\n        \"--timeline-reveal-index\": orderRef.current.get(id) ?? 0,\n        \"--timeline-reveal-delay\": `${options.initialDelay + Math.min(options.maxStagger ?? Infinity, (orderRef.current.get(id) ?? 0) * options.interval)}ms`,\n        \"--timeline-reveal-interval\": `${options.interval}ms`,\n        \"--timeline-reveal-duration\": `${options.duration}ms`,\n        \"--timeline-reveal-initial-delay\": `${options.initialDelay}ms`,\n      },\n    };\n  };\n\n  return { getItemRevealProps, handleAnimationEnd };\n}\n\nfunction TimelineInner<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n>(\n  {\n    className,\n    items,\n    mode = \"events\",\n    orientation,\n    layout,\n    scale,\n    order = \"asc\",\n    interactive,\n    presentation,\n    reveal = \"none\",\n    revealOptions,\n    revealCount,\n    onItemReveal,\n    onRevealComplete,\n    viewport,\n    selectedId,\n    defaultSelectedId = null,\n    onSelectedIdChange,\n    renderItem,\n    variant = \"activity\",\n    renderDetails,\n    expandedIds,\n    defaultExpandedIds = [],\n    onExpandedIdsChange,\n    getGroup,\n    ...props\n  }: TimelineProps<TPayload>,\n  ref: ForwardedRef<HTMLElement>,\n) {\n  const hydrated = useHydrated();\n  const [internalExpandedIds, setInternalExpandedIds] =\n    useState(defaultExpandedIds);\n  const currentExpandedIds = expandedIds ?? internalExpandedIds;\n  const toggleExpanded = (id: string) => {\n    const next = currentExpandedIds.includes(id)\n      ? currentExpandedIds.filter((value) => value !== id)\n      : [...currentExpandedIds, id];\n    if (expandedIds === undefined) setInternalExpandedIds(next);\n    onExpandedIdsChange?.(next);\n  };\n  const isStoryMode = mode === \"story\";\n  const resolvedLayout = layout ?? (isStoryMode ? \"story\" : \"rail\");\n  const resolvedPresentation = resolveTimelinePresentation(\n    presentation,\n    mode,\n    resolvedLayout,\n  );\n  const usesFlowRenderer = resolvedPresentation === \"flow\";\n  // Story styling (large editorial typography) is distinct from the flow\n  // renderer: compact events/progress timelines can render in flow too.\n  const isStoryStyling = isStoryMode || resolvedLayout === \"story\";\n  const resolvedOrientation = usesFlowRenderer\n    ? \"vertical\"\n    : (orientation ?? \"horizontal\");\n  const resolvedScale = scale ?? (isStoryMode ? \"sequence\" : \"auto\");\n  const resolvedInteractive = interactive ?? !isStoryStyling;\n  const resolvedViewport = usesFlowRenderer\n    ? { controls: false, wheelZoom: false as const, ...viewport }\n    : viewport;\n  const flowListLayout =\n    resolvedLayout === \"alternating\" ? \"alternating\" : \"default\";\n  const revealActive = isTimelineRevealActive(reveal, resolvedPresentation);\n  const revealOptionsNormalized = useMemo(\n    () => normalizeTimelineRevealOptions(revealOptions),\n    // Depend on the individual fields so a fresh options object literal does\n    // not re-run reveal effects on every render.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [\n      revealOptions?.trigger,\n      revealOptions?.interval,\n      revealOptions?.duration,\n      revealOptions?.initialDelay,\n    ],\n  );\n  const rootLabel = props[\"aria-label\"];\n  const viewportLabel =\n    typeof rootLabel === \"string\" && rootLabel.length > 0\n      ? `${rootLabel} viewport`\n      : \"Timeline viewport\";\n  const normalizedItems = useMemo(\n    () =>\n      normalizeTimelineItems(items, {\n        mode,\n        orientation: resolvedOrientation,\n        layout: resolvedLayout,\n        scale: resolvedScale,\n        order,\n      }),\n    [items, mode, order, resolvedLayout, resolvedOrientation, resolvedScale],\n  );\n  const dataScale = useMemo(\n    () =>\n      normalizedItems.length > 0 &&\n      normalizedItems.every((item) => item.dateValue !== undefined)\n        ? resolvedScale === \"sequence\"\n          ? \"sequence\"\n          : \"time\"\n        : \"sequence\",\n    [normalizedItems, resolvedScale],\n  );\n  const contentSize = useMemo(\n    () =>\n      getTimelineContentSize(\n        normalizedItems,\n        resolvedOrientation,\n        resolvedLayout,\n      ),\n    [normalizedItems, resolvedLayout, resolvedOrientation],\n  );\n  const [internalSelectedId, setInternalSelectedId] = useState<string | null>(\n    defaultSelectedId,\n  );\n  const currentSelectedId =\n    selectedId === undefined ? internalSelectedId : selectedId;\n  const selectedPoint = normalizedItems.find(\n    (item) => item.id === currentSelectedId,\n  )?.point;\n  const setSelectedId = useCallback(\n    (id: string | null) => {\n      const nextId =\n        id && normalizedItems.some((item) => item.id === id && !item.disabled)\n          ? id\n          : null;\n\n      if (selectedId === undefined) {\n        setInternalSelectedId(nextId);\n      }\n\n      if (nextId !== currentSelectedId) {\n        onSelectedIdChange?.(nextId);\n      }\n    },\n    [currentSelectedId, normalizedItems, onSelectedIdChange, selectedId],\n  );\n  const getPreviousId = useCallback(\n    () => getNextEnabledTimelineItemId(normalizedItems, currentSelectedId, -1),\n    [currentSelectedId, normalizedItems],\n  );\n  const getNextId = useCallback(\n    () => getNextEnabledTimelineItemId(normalizedItems, currentSelectedId, 1),\n    [currentSelectedId, normalizedItems],\n  );\n  const getFirstId = useCallback(\n    () => getEdgeEnabledTimelineItemId(normalizedItems, \"first\"),\n    [normalizedItems],\n  );\n  const getLastId = useCallback(\n    () => getEdgeEnabledTimelineItemId(normalizedItems, \"last\"),\n    [normalizedItems],\n  );\n  const itemIds = useMemo(\n    () => normalizedItems.map((item) => item.id),\n    [normalizedItems],\n  );\n  const { getItemRevealProps, handleAnimationEnd } = useTimelineReveal({\n    itemIds,\n    reveal,\n    active: revealActive,\n    options: revealOptionsNormalized,\n    revealCount,\n    onItemReveal,\n    onRevealComplete,\n  });\n  const railRevealActive = revealActive && reveal === \"stagger\";\n  const railStyle: TimelineRevealItemStyle | undefined = railRevealActive\n    ? {\n        \"--timeline-rail-duration\": `${getTimelineRevealBatchDuration({\n          batchSize: normalizedItems.length,\n          reveal,\n          options: revealOptionsNormalized,\n        })}ms`,\n        \"--timeline-rail-delay\": `${revealOptionsNormalized.initialDelay}ms`,\n      }\n    : undefined;\n\n  return (\n    <section\n      {...props}\n      ref={ref}\n      data-slot=\"timeline\"\n      data-mode={mode}\n      data-variant={variant}\n      data-reveal-ready={hydrated ? \"true\" : undefined}\n      data-orientation={resolvedOrientation}\n      data-layout={resolvedLayout}\n      data-scale={dataScale}\n      data-interactive={resolvedInteractive ? \"true\" : \"false\"}\n      data-presentation={resolvedPresentation}\n      className={cn(timelineRootClasses, className)}\n    >\n      {usesFlowRenderer ? (\n        <TimelineFlowViewport\n          aria-label={viewportLabel}\n          interactive={resolvedInteractive}\n          orientation={resolvedOrientation}\n          layout={resolvedLayout}\n          presentation={resolvedPresentation}\n          viewport={resolvedViewport}\n          onNavigate={setSelectedId}\n          getPreviousId={getPreviousId}\n          getNextId={getNextId}\n          getFirstId={getFirstId}\n          getLastId={getLastId}\n        >\n          <ol\n            data-slot=\"timeline-list\"\n            data-reveal={revealActive ? reveal : undefined}\n            data-reveal-rail={railRevealActive ? \"true\" : undefined}\n            onAnimationEnd={revealActive ? handleAnimationEnd : undefined}\n            className={cn(\n              isStoryStyling\n                ? timelineStoryListClasses\n                : timelineFlowListClasses,\n              (isStoryStyling\n                ? timelineStoryListLayoutClasses\n                : timelineFlowListLayoutClasses)[flowListLayout],\n            )}\n            style={railStyle}\n          >\n            {normalizedItems.map((item, index) => {\n              const {\n                ref: revealRef,\n                style: revealStyle,\n                ...revealAttrs\n              } = getItemRevealProps(\n                item.id,\n                index === normalizedItems.length - 1,\n              );\n\n              const group = getGroup?.(item);\n              const previousGroup =\n                index > 0 ? getGroup?.(normalizedItems[index - 1]) : null;\n              const groupStart = group && group.id !== previousGroup?.id;\n              return (\n                <Fragment key={item.id}>\n                  {groupStart ? (\n                    <li\n                      role=\"presentation\"\n                      data-slot=\"timeline-group\"\n                      className=\"bg-background relative z-10 py-4 text-sm font-semibold\"\n                    >\n                      <span role=\"heading\" aria-level={3}>\n                        {group.label}\n                      </span>\n                    </li>\n                  ) : null}\n                  <TimelineItem\n                    key={item.id}\n                    ref={revealRef}\n                    style={revealStyle}\n                    item={item}\n                    mode={mode}\n                    orientation={resolvedOrientation}\n                    layout={resolvedLayout}\n                    presentation={resolvedPresentation}\n                    interactive={resolvedInteractive}\n                    selected={item.id === currentSelectedId}\n                    renderItem={renderItem}\n                    variant={variant}\n                    details={renderDetails ? renderDetails(item) : item.details}\n                    expanded={currentExpandedIds.includes(item.id)}\n                    onExpandedChange={() => toggleExpanded(item.id)}\n                    onSelect={setSelectedId}\n                    {...revealAttrs}\n                  />\n                </Fragment>\n              );\n            })}\n          </ol>\n        </TimelineFlowViewport>\n      ) : (\n        <TimelineViewport\n          aria-label={viewportLabel}\n          contentSize={contentSize}\n          interactive={resolvedInteractive}\n          orientation={resolvedOrientation}\n          layout={resolvedLayout}\n          presentation={resolvedPresentation}\n          viewport={resolvedViewport}\n          selectedPoint={selectedPoint}\n          onNavigate={setSelectedId}\n          getPreviousId={getPreviousId}\n          getNextId={getNextId}\n          getFirstId={getFirstId}\n          getLastId={getLastId}\n        >\n          <TimelineRail\n            items={normalizedItems}\n            orientation={resolvedOrientation}\n          />\n          <ol\n            data-slot=\"timeline-list\"\n            className={timelineListClasses}\n            style={{ width: contentSize.width, height: contentSize.height }}\n          >\n            {normalizedItems.map((item) => (\n              <TimelineItem\n                key={item.id}\n                item={item}\n                mode={mode}\n                orientation={resolvedOrientation}\n                layout={resolvedLayout}\n                presentation={resolvedPresentation}\n                interactive={resolvedInteractive}\n                selected={item.id === currentSelectedId}\n                renderItem={renderItem}\n                onSelect={setSelectedId}\n              />\n            ))}\n          </ol>\n        </TimelineViewport>\n      )}\n    </section>\n  );\n}\n\nexport const Timeline = forwardRef(TimelineInner) as (<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n>(\n  props: TimelineProps<TPayload> & RefAttributes<HTMLElement>,\n) => ReactElement | null) & { displayName?: string };\n\nTimeline.displayName = \"Timeline\";\n"
    },
    {
      "path": "packages/components/src/components/timeline/timeline-utils.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/timeline/timeline-utils.ts",
      "content": "import type { ReactNode } from \"react\";\n\nexport type TimelineStatus =\n  \"neutral\" | \"complete\" | \"current\" | \"upcoming\" | \"warning\" | \"error\";\n\nexport type TimelineMode = \"events\" | \"progress\" | \"story\";\nexport type TimelineOrientation = \"horizontal\" | \"vertical\";\nexport type TimelineLayout = \"rail\" | \"alternating\" | \"stacked\" | \"story\";\nexport type TimelineScale = \"auto\" | \"time\" | \"sequence\";\nexport type TimelineResolvedScale = \"time\" | \"sequence\";\nexport type TimelineOrder = \"asc\" | \"desc\";\nexport type TimelineWheelZoom = \"modifier\" | \"always\" | false;\nexport type TimelineViewportChrome = \"none\" | \"subtle\" | \"panel\";\nexport type TimelineControlsVisibility = \"always\" | \"hover\";\n\nexport type TimelinePresentation = \"canvas\" | \"flow\";\nexport type TimelineVariant = \"activity\" | \"cards\";\nexport type TimelineGroup = { id: string; label: ReactNode };\nexport type TimelineRevealMode = \"none\" | \"stagger\" | \"all\";\nexport type TimelineRevealTrigger = \"mount\" | \"in-view\" | \"manual\";\n\nexport type TimelineRevealOptions = {\n  trigger?: TimelineRevealTrigger;\n  interval?: number;\n  duration?: number;\n  initialDelay?: number;\n};\n\nexport type NormalizedTimelineRevealOptions = {\n  trigger: TimelineRevealTrigger;\n  interval: number;\n  duration: number;\n  initialDelay: number;\n  /** Undefined preserves explicitly supplied stagger timing. */\n  maxStagger?: number;\n};\n\nexport type TimelineImage = {\n  src: string;\n  alt: string;\n  width?: number;\n  height?: number;\n};\n\nexport type TimelineItemPayload = Record<string, unknown>;\n\nexport type TimelineItemBaseData = {\n  id: string;\n  datetime?: string | Date;\n  dateLabel?: ReactNode;\n  status?: TimelineStatus;\n  marker?: ReactNode;\n  disabled?: boolean;\n};\n\nexport type TimelineItemDefaultContent = {\n  title?: ReactNode;\n  description?: ReactNode;\n  image?: TimelineImage;\n  details?: ReactNode;\n};\n\nexport type TimelineItemData<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n> = TimelineItemBaseData &\n  TimelineItemDefaultContent & {\n    data?: TPayload;\n  };\n\nexport type TimelineTransform = {\n  x: number;\n  y: number;\n  zoom: number;\n};\n\nexport type TimelinePoint = {\n  x: number;\n  y: number;\n};\n\nexport type TimelineViewportOptions = {\n  defaultZoom?: number;\n  minZoom?: number;\n  maxZoom?: number;\n  controls?: boolean;\n  controlsVisibility?: TimelineControlsVisibility;\n  chrome?: TimelineViewportChrome;\n  wheelZoom?: TimelineWheelZoom;\n};\n\nexport type NormalizedTimelineItem<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n> = TimelineItemData<TPayload> & {\n  index: number;\n  originalIndex: number;\n  status: TimelineStatus;\n  datetimeAttribute?: string;\n  dateValue?: number;\n  point: TimelinePoint;\n};\n\nexport type NormalizeTimelineItemsOptions = {\n  mode?: TimelineMode;\n  orientation?: TimelineOrientation;\n  layout?: TimelineLayout;\n  scale?: TimelineScale;\n  order?: TimelineOrder;\n};\n\nexport const timelineDefaultViewport = {\n  defaultZoom: 1,\n  minZoom: 0.5,\n  maxZoom: 2.5,\n  controls: true,\n  controlsVisibility: \"hover\" as TimelineControlsVisibility,\n  chrome: \"none\" as TimelineViewportChrome,\n  wheelZoom: \"modifier\" as TimelineWheelZoom,\n};\n\nexport const timelineDefaultRevealOptions: NormalizedTimelineRevealOptions = {\n  trigger: \"mount\",\n  interval: 60,\n  duration: 220,\n  initialDelay: 0,\n};\n\nexport const timelineGeometry = {\n  itemGap: 420,\n  minimumItemGap: 360,\n  startPadding: 200,\n  horizontalCardOffset: 72,\n  verticalCardOffset: 72,\n  horizontalRailY: 132,\n  horizontalAlternatingRailY: 416,\n  verticalRailX: 160,\n  verticalAlternatingRailX: 460,\n  cardWidth: 320,\n  estimatedCardBlockSize: 340,\n};\n\nexport function clampNumber(value: number, min: number, max: number) {\n  return Math.min(Math.max(value, min), max);\n}\n\nexport function normalizeViewportOptions(\n  viewport: TimelineViewportOptions | undefined,\n) {\n  const minZoom = viewport?.minZoom ?? timelineDefaultViewport.minZoom;\n  const maxZoom = Math.max(\n    minZoom,\n    viewport?.maxZoom ?? timelineDefaultViewport.maxZoom,\n  );\n  const defaultZoom = clampNumber(\n    viewport?.defaultZoom ?? timelineDefaultViewport.defaultZoom,\n    minZoom,\n    maxZoom,\n  );\n\n  return {\n    defaultZoom,\n    minZoom,\n    maxZoom,\n    controls: viewport?.controls ?? timelineDefaultViewport.controls,\n    controlsVisibility:\n      viewport?.controlsVisibility ??\n      timelineDefaultViewport.controlsVisibility,\n    chrome: viewport?.chrome ?? timelineDefaultViewport.chrome,\n    wheelZoom: viewport?.wheelZoom ?? timelineDefaultViewport.wheelZoom,\n  };\n}\n\nexport function resolveTimelinePresentation(\n  presentation: TimelinePresentation | undefined,\n  mode: TimelineMode = \"events\",\n  layout: TimelineLayout = \"rail\",\n): TimelinePresentation {\n  // Story styling always renders in normal document flow. `presentation=\"canvas\"`\n  // is intentionally not supported for story-styled timelines, so it is ignored.\n  if (mode === \"story\" || layout === \"story\") {\n    return \"flow\";\n  }\n\n  return presentation ?? \"flow\";\n}\n\nexport function normalizeTimelineRevealOptions(\n  options: TimelineRevealOptions | undefined,\n): NormalizedTimelineRevealOptions {\n  return {\n    maxStagger: options?.interval === undefined ? 300 : undefined,\n    trigger: options?.trigger ?? timelineDefaultRevealOptions.trigger,\n    interval: Math.max(\n      0,\n      options?.interval ?? timelineDefaultRevealOptions.interval,\n    ),\n    duration: Math.max(\n      0,\n      options?.duration ?? timelineDefaultRevealOptions.duration,\n    ),\n    initialDelay: Math.max(\n      0,\n      options?.initialDelay ?? timelineDefaultRevealOptions.initialDelay,\n    ),\n  };\n}\n\nexport function isTimelineRevealActive(\n  reveal: TimelineRevealMode | undefined,\n  presentation: TimelinePresentation,\n): boolean {\n  return reveal !== undefined && reveal !== \"none\" && presentation === \"flow\";\n}\n\nexport function clampTimelineRevealCount(\n  revealCount: number | undefined,\n  total: number,\n): number {\n  const max = Math.max(0, total);\n\n  if (revealCount === undefined) {\n    return 0;\n  }\n\n  if (!Number.isFinite(revealCount)) {\n    return max;\n  }\n\n  return clampNumber(Math.floor(revealCount), 0, max);\n}\n\nexport function getTimelineRevealItemDelay({\n  batchOrder,\n  reveal,\n  options,\n}: {\n  batchOrder: number;\n  reveal: TimelineRevealMode;\n  options: NormalizedTimelineRevealOptions;\n}): number {\n  if (reveal !== \"stagger\") {\n    return options.initialDelay;\n  }\n\n  return (\n    options.initialDelay +\n    Math.min(\n      options.maxStagger ?? Infinity,\n      Math.max(0, batchOrder) * options.interval,\n    )\n  );\n}\n\nexport function getTimelineRevealBatchDuration({\n  batchSize,\n  reveal,\n  options,\n}: {\n  batchSize: number;\n  reveal: TimelineRevealMode;\n  options: NormalizedTimelineRevealOptions;\n}): number {\n  if (batchSize <= 0) {\n    return 0;\n  }\n\n  if (reveal !== \"stagger\") {\n    return options.initialDelay + options.duration;\n  }\n\n  return (\n    options.initialDelay +\n    Math.min(\n      options.maxStagger ?? Infinity,\n      Math.max(0, batchSize - 1) * options.interval,\n    ) +\n    options.duration\n  );\n}\n\nexport function getTimelineDateValue(\n  datetime: TimelineItemBaseData[\"datetime\"],\n) {\n  if (!datetime) {\n    return undefined;\n  }\n\n  const date = datetime instanceof Date ? datetime : new Date(datetime);\n  const value = date.getTime();\n\n  return Number.isNaN(value) ? undefined : value;\n}\n\nexport function getTimelineDatetimeAttribute(\n  datetime: TimelineItemBaseData[\"datetime\"],\n) {\n  if (!datetime) {\n    return undefined;\n  }\n\n  if (datetime instanceof Date) {\n    return Number.isNaN(datetime.getTime())\n      ? undefined\n      : datetime.toISOString();\n  }\n\n  return getTimelineDateValue(datetime) === undefined ? undefined : datetime;\n}\n\nexport function resolveTimelineScale<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n>(items: TimelineItemData<TPayload>[], scale: TimelineScale = \"auto\") {\n  if (scale !== \"auto\") {\n    return scale;\n  }\n\n  return items.length > 0 &&\n    items.every((item) => getTimelineDateValue(item.datetime) !== undefined)\n    ? \"time\"\n    : \"sequence\";\n}\n\nfunction shouldSortByDate(mode: TimelineMode, scale: TimelineResolvedScale) {\n  return (mode === \"events\" || mode === \"story\") && scale === \"time\";\n}\n\nfunction getTimelineCrossAxisPosition(\n  orientation: TimelineOrientation,\n  layout: TimelineLayout,\n) {\n  if (layout === \"story\") {\n    return orientation === \"horizontal\"\n      ? timelineGeometry.horizontalRailY\n      : timelineGeometry.verticalRailX;\n  }\n\n  if (orientation === \"horizontal\") {\n    return layout === \"alternating\"\n      ? timelineGeometry.horizontalAlternatingRailY\n      : timelineGeometry.horizontalRailY;\n  }\n\n  return layout === \"alternating\"\n    ? timelineGeometry.verticalAlternatingRailX\n    : timelineGeometry.verticalRailX;\n}\n\nfunction getRawAxisPosition<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n>(\n  item: TimelineItemData<TPayload>,\n  index: number,\n  allItems: TimelineItemData<TPayload>[],\n  scale: TimelineResolvedScale,\n  order: TimelineOrder,\n) {\n  if (scale === \"sequence\" || allItems.length <= 1) {\n    return index * timelineGeometry.itemGap;\n  }\n\n  const values = allItems\n    .map((candidate) => getTimelineDateValue(candidate.datetime))\n    .filter((value): value is number => value !== undefined);\n\n  if (values.length !== allItems.length) {\n    return index * timelineGeometry.itemGap;\n  }\n\n  const min = Math.min(...values);\n  const max = Math.max(...values);\n\n  if (min === max) {\n    return index * timelineGeometry.itemGap;\n  }\n\n  const value = getTimelineDateValue(item.datetime) ?? min;\n  const axisLength = (allItems.length - 1) * timelineGeometry.itemGap;\n  const position = ((value - min) / (max - min)) * axisLength;\n\n  return order === \"desc\" ? axisLength - position : position;\n}\n\nfunction getAxisPositions<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n>(\n  items: TimelineItemData<TPayload>[],\n  scale: TimelineResolvedScale,\n  order: TimelineOrder,\n) {\n  const rawPositions = items.map((item, index) =>\n    getRawAxisPosition(item, index, items, scale, order),\n  );\n\n  return rawPositions.reduce<number[]>((positions, position, index) => {\n    if (index === 0) {\n      positions.push(position);\n      return positions;\n    }\n\n    positions.push(\n      Math.max(\n        position,\n        positions[index - 1] + timelineGeometry.minimumItemGap,\n      ),\n    );\n    return positions;\n  }, []);\n}\n\nfunction getTimelinePoint({\n  axisPosition,\n  orientation,\n  layout,\n}: {\n  axisPosition: number;\n  orientation: TimelineOrientation;\n  layout: TimelineLayout;\n}) {\n  if (orientation === \"horizontal\") {\n    return {\n      x: timelineGeometry.startPadding + axisPosition,\n      y: getTimelineCrossAxisPosition(orientation, layout),\n    };\n  }\n\n  return {\n    x: getTimelineCrossAxisPosition(orientation, layout),\n    y: timelineGeometry.startPadding + axisPosition,\n  };\n}\n\nexport function normalizeTimelineItems<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n>(\n  items: TimelineItemData<TPayload>[],\n  {\n    mode = \"events\",\n    orientation = \"horizontal\",\n    layout = \"rail\",\n    scale = \"auto\",\n    order = \"asc\",\n  }: NormalizeTimelineItemsOptions = {},\n) {\n  const resolvedScale = resolveTimelineScale(items, scale);\n  const withOriginalIndex = items.map((item, originalIndex) => ({\n    ...item,\n    originalIndex,\n  }));\n  const sortedItems = shouldSortByDate(mode, resolvedScale)\n    ? [...withOriginalIndex].sort((a, b) => {\n        const aValue = getTimelineDateValue(a.datetime) ?? 0;\n        const bValue = getTimelineDateValue(b.datetime) ?? 0;\n\n        if (aValue === bValue) {\n          return a.originalIndex - b.originalIndex;\n        }\n\n        return aValue - bValue;\n      })\n    : withOriginalIndex;\n  const orderedItems =\n    order === \"desc\" ? [...sortedItems].reverse() : sortedItems;\n  const axisPositions = getAxisPositions(orderedItems, resolvedScale, order);\n\n  return orderedItems.map((item, index): NormalizedTimelineItem<TPayload> => {\n    const axisPosition = axisPositions[index];\n\n    return {\n      ...item,\n      index,\n      status: item.status ?? \"neutral\",\n      datetimeAttribute: getTimelineDatetimeAttribute(item.datetime),\n      dateValue: getTimelineDateValue(item.datetime),\n      point: getTimelinePoint({ axisPosition, orientation, layout }),\n    };\n  });\n}\n\nexport function getTimelineContentSize<\n  TPayload extends TimelineItemPayload = TimelineItemPayload,\n>(\n  items: NormalizedTimelineItem<TPayload>[],\n  orientation: TimelineOrientation,\n  layout: TimelineLayout,\n) {\n  if (items.length === 0) {\n    return {\n      width: orientation === \"horizontal\" ? 640 : 520,\n      height: orientation === \"horizontal\" ? 360 : 520,\n    };\n  }\n\n  const maxX = Math.max(...items.map((item) => item.point.x));\n  const maxY = Math.max(...items.map((item) => item.point.y));\n  const minX = Math.min(...items.map((item) => item.point.x));\n  const minY = Math.min(...items.map((item) => item.point.y));\n\n  if (orientation === \"horizontal\") {\n    const railY = getTimelineCrossAxisPosition(orientation, layout);\n    const cardInset =\n      timelineGeometry.cardWidth / 2 + timelineGeometry.startPadding / 2;\n    const cardBlock =\n      timelineGeometry.estimatedCardBlockSize +\n      timelineGeometry.horizontalCardOffset;\n\n    return {\n      width: Math.ceil(maxX - Math.min(0, minX) + cardInset),\n      height: Math.ceil(\n        layout === \"alternating\"\n          ? railY + cardBlock\n          : railY + cardBlock + timelineGeometry.startPadding / 2,\n      ),\n    };\n  }\n\n  const cardInset =\n    timelineGeometry.cardWidth +\n    timelineGeometry.verticalCardOffset +\n    timelineGeometry.startPadding;\n\n  return {\n    width: Math.ceil(maxX - Math.min(0, minX) + cardInset),\n    height: Math.ceil(\n      maxY -\n        Math.min(0, minY) +\n        timelineGeometry.estimatedCardBlockSize +\n        timelineGeometry.startPadding / 2,\n    ),\n  };\n}\n\nexport function getDefaultTimelineTransform(\n  viewport?: TimelineViewportOptions,\n): TimelineTransform {\n  const options = normalizeViewportOptions(viewport);\n\n  return { x: 0, y: 0, zoom: options.defaultZoom };\n}\n\nexport function zoomTimelineTransform({\n  transform,\n  delta,\n  minZoom,\n  maxZoom,\n  origin = { x: 0, y: 0 },\n}: {\n  transform: TimelineTransform;\n  delta: number;\n  minZoom: number;\n  maxZoom: number;\n  origin?: TimelinePoint;\n}): TimelineTransform {\n  const nextZoom = clampNumber(transform.zoom + delta, minZoom, maxZoom);\n  const zoomRatio = nextZoom / transform.zoom;\n\n  return {\n    zoom: nextZoom,\n    x: origin.x - (origin.x - transform.x) * zoomRatio,\n    y: origin.y - (origin.y - transform.y) * zoomRatio,\n  };\n}\n\nexport function resetTimelineTransform(\n  viewport?: TimelineViewportOptions,\n): TimelineTransform {\n  return getDefaultTimelineTransform(viewport);\n}\n\nexport function fitTimelineTransform({\n  contentSize,\n  viewportSize,\n  minZoom,\n  maxZoom,\n}: {\n  contentSize: { width: number; height: number };\n  viewportSize: { width: number; height: number };\n  minZoom: number;\n  maxZoom: number;\n}): TimelineTransform {\n  if (\n    contentSize.width <= 0 ||\n    contentSize.height <= 0 ||\n    viewportSize.width <= 0 ||\n    viewportSize.height <= 0\n  ) {\n    return { x: 0, y: 0, zoom: clampNumber(1, minZoom, maxZoom) };\n  }\n\n  const zoom = clampNumber(\n    Math.min(\n      viewportSize.width / contentSize.width,\n      viewportSize.height / contentSize.height,\n    ),\n    minZoom,\n    maxZoom,\n  );\n\n  return {\n    zoom,\n    x: (viewportSize.width - contentSize.width * zoom) / 2,\n    y: (viewportSize.height - contentSize.height * zoom) / 2,\n  };\n}\n\nexport function centerTimelinePoint({\n  point,\n  viewportSize,\n  zoom,\n}: {\n  point: TimelinePoint;\n  viewportSize: { width: number; height: number };\n  zoom: number;\n}): TimelineTransform {\n  return {\n    zoom,\n    x: viewportSize.width / 2 - point.x * zoom,\n    y: viewportSize.height / 2 - point.y * zoom,\n  };\n}\n\nexport function getNextEnabledTimelineItemId(\n  items: Array<Pick<NormalizedTimelineItem, \"id\" | \"disabled\">>,\n  selectedId: string | null | undefined,\n  direction: 1 | -1,\n) {\n  const enabledItems = items.filter((item) => !item.disabled);\n\n  if (enabledItems.length === 0) {\n    return null;\n  }\n\n  const currentIndex = enabledItems.findIndex((item) => item.id === selectedId);\n\n  if (currentIndex === -1) {\n    return direction === 1\n      ? enabledItems[0].id\n      : enabledItems[enabledItems.length - 1].id;\n  }\n\n  return enabledItems[\n    clampNumber(currentIndex + direction, 0, enabledItems.length - 1)\n  ].id;\n}\n\nexport function getEdgeEnabledTimelineItemId(\n  items: Array<Pick<NormalizedTimelineItem, \"id\" | \"disabled\">>,\n  edge: \"first\" | \"last\",\n) {\n  const enabledItems = items.filter((item) => !item.disabled);\n\n  if (enabledItems.length === 0) {\n    return null;\n  }\n\n  return edge === \"first\"\n    ? enabledItems[0].id\n    : enabledItems[enabledItems.length - 1].id;\n}\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
    },
    {
      "path": "packages/components/src/utils/use-hydrated.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/use-hydrated.ts",
      "content": "import { useSyncExternalStore } from \"react\";\n\nconst subscribe = () => () => {};\nconst clientSnapshot = () => true;\nconst serverSnapshot = () => false;\n\n/** Keep browser preferences out of the first hydration render. */\nexport function useHydrated() {\n  return useSyncExternalStore(subscribe, clientSnapshot, serverSnapshot);\n}\n"
    },
    {
      "path": "packages/components/src/components/timeline/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/timeline/index.ts",
      "content": "export {\n  Timeline,\n  TimelineControls,\n  TimelineItem,\n  TimelineViewport,\n  type TimelineControlsProps,\n  type TimelineItemProps,\n  type TimelineItemRenderer,\n  type TimelineProps,\n  type TimelineViewportProps,\n} from \"./timeline\";\nexport {\n  centerTimelinePoint,\n  clampNumber,\n  clampTimelineRevealCount,\n  fitTimelineTransform,\n  getDefaultTimelineTransform,\n  getEdgeEnabledTimelineItemId,\n  getNextEnabledTimelineItemId,\n  getTimelineContentSize,\n  getTimelineDatetimeAttribute,\n  getTimelineDateValue,\n  getTimelineRevealBatchDuration,\n  getTimelineRevealItemDelay,\n  isTimelineRevealActive,\n  normalizeTimelineItems,\n  normalizeTimelineRevealOptions,\n  normalizeViewportOptions,\n  resetTimelineTransform,\n  resolveTimelinePresentation,\n  resolveTimelineScale,\n  timelineDefaultRevealOptions,\n  timelineDefaultViewport,\n  timelineGeometry,\n  zoomTimelineTransform,\n  type NormalizedTimelineItem,\n  type NormalizedTimelineRevealOptions,\n  type TimelineImage,\n  type TimelineVariant,\n  type TimelineGroup,\n  type TimelineItemBaseData,\n  type TimelineItemData,\n  type TimelineItemDefaultContent,\n  type TimelineItemPayload,\n  type TimelineLayout,\n  type TimelineMode,\n  type TimelineOrder,\n  type TimelineOrientation,\n  type TimelinePoint,\n  type TimelinePresentation,\n  type TimelineRevealMode,\n  type TimelineRevealOptions,\n  type TimelineRevealTrigger,\n  type TimelineScale,\n  type TimelineControlsVisibility,\n  type TimelineStatus,\n  type TimelineTransform,\n  type TimelineViewportChrome,\n  type TimelineViewportOptions,\n  type TimelineWheelZoom,\n} from \"./timeline-utils\";\n"
    }
  ]
}
