{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "layouts-calendar",
  "type": "registry:block",
  "title": "Calendar",
  "description": "Full-featured week/day view with event drag + resize, multi-day events, mini-calendar sidebar, command menu, and rich keyboard shortcuts.",
  "dependencies": [
    "date-fns",
    "lucide-react",
    "next-themes",
    "react-day-picker",
    "cmdk",
    "motion"
  ],
  "files": [
    {
      "path": "components/layouts/calendar/calendar-event-item.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"@/lib/utils\";\nimport { format, isPast } from \"date-fns\";\nimport {\n  Popover,\n  PopoverAnchor,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { EventDetailPopover } from \"./event-detail-popover\";\nimport { useCalendarPopoverBoundary } from \"./calendar-popover-context\";\nimport type {\n  CalendarEvent,\n  CalendarEventItemProps,\n  EventColor,\n} from \"./week-view-types\";\nimport { EventContextMenu } from \"./event-context-menu\";\n\nexport const eventColorStyles: Record<\n  EventColor,\n  {\n    bg: string;\n    bgHover: string;\n    border: string;\n    borderLine: string;\n    text: string;\n  }\n> = {\n  red: {\n    bg: \"bg-event-red-bg\",\n    bgHover: \"hover:bg-event-red-bg/70\",\n    border: \"bg-event-red-border\",\n    borderLine: \"border-event-red-border\",\n    text: \"text-event-red\",\n  },\n  orange: {\n    bg: \"bg-event-orange-bg\",\n    bgHover: \"hover:bg-event-orange-bg/70\",\n    border: \"bg-event-orange-border\",\n    borderLine: \"border-event-orange-border\",\n    text: \"text-event-orange\",\n  },\n  yellow: {\n    bg: \"bg-event-yellow-bg\",\n    bgHover: \"hover:bg-event-yellow-bg/70\",\n    border: \"bg-event-yellow-border\",\n    borderLine: \"border-event-yellow-border\",\n    text: \"text-event-yellow\",\n  },\n  green: {\n    bg: \"bg-event-green-bg\",\n    bgHover: \"hover:bg-event-green-bg/70\",\n    border: \"bg-event-green-border\",\n    borderLine: \"border-event-green-border\",\n    text: \"text-event-green\",\n  },\n  blue: {\n    bg: \"bg-event-blue-bg\",\n    bgHover: \"hover:bg-event-blue-bg/70\",\n    border: \"bg-event-blue-border\",\n    borderLine: \"border-event-blue-border\",\n    text: \"text-event-blue\",\n  },\n  purple: {\n    bg: \"bg-event-purple-bg\",\n    bgHover: \"hover:bg-event-purple-bg/70\",\n    border: \"bg-event-purple-border\",\n    borderLine: \"border-event-purple-border\",\n    text: \"text-event-purple\",\n  },\n  gray: {\n    bg: \"bg-event-gray-bg\",\n    bgHover: \"hover:bg-event-gray-bg/70\",\n    border: \"bg-event-gray-border\",\n    borderLine: \"border-event-gray-border\",\n    text: \"text-event-gray\",\n  },\n};\n\n/**\n * Formats time showing only minutes if not on the hour\n * e.g., \"10\" for 10:00, \"2:45\" for 2:45\n */\nfunction formatTimeShort(date: Date): string {\n  const minutes = date.getMinutes();\n  if (minutes === 0) {\n    return format(date, \"h\");\n  }\n  return format(date, \"h:mm\");\n}\n\n/**\n * Formats event time as a compact range like \"10–11 AM\" or \"11 AM–2 PM\"\n */\nfunction formatEventTimeRange(event: CalendarEvent): string {\n  const startTime = formatTimeShort(event.start);\n  const endTime = formatTimeShort(event.end);\n  const endPeriod = format(event.end, \"a\");\n  const startPeriod = format(event.start, \"a\");\n\n  // If same period (both AM or both PM), only show period at the end\n  if (startPeriod === endPeriod) {\n    return `${startTime}\\u2013${endTime} ${endPeriod}`;\n  }\n\n  // Different periods, show both\n  return `${startTime} ${startPeriod}\\u2013${endTime} ${endPeriod}`;\n}\n\nfunction computeOverrideStyle(\n  positionedEvent: CalendarEventItemProps[\"positionedEvent\"],\n  hourHeight: number,\n  overrideStart: Date,\n  overrideEnd: Date,\n) {\n  const startMinutes =\n    overrideStart.getHours() * 60 + overrideStart.getMinutes();\n  let endMinutes = overrideEnd.getHours() * 60 + overrideEnd.getMinutes();\n  // If end is midnight and on a different day than start, treat as 1440 (end of day)\n  if (endMinutes === 0 && overrideEnd.getDate() !== overrideStart.getDate()) {\n    endMinutes = 1440;\n  }\n  const topPx = (startMinutes / 60) * hourHeight;\n  const heightPx = ((endMinutes - startMinutes) / 60) * hourHeight;\n\n  return {\n    top: `${topPx}px`,\n    height: `${heightPx}px`,\n    left: `${positionedEvent.left}%`,\n    width: `${positionedEvent.width}%`,\n    minHeight: \"20px\",\n  };\n}\n\nconst RESIZE_HOTZONE_PX = 8;\n\nexport function CalendarEventItem({\n  positionedEvent,\n  hourHeight,\n  isPast: isPastProp,\n  isSelected,\n  onClick,\n  dragVariant = \"default\",\n  overrideStart,\n  overrideEnd,\n  onDragMouseDown,\n  onResizeMouseDown,\n  onEventChange,\n  cursorY,\n  cursorX,\n  fixedWidth,\n  fixedHeight,\n  onContextMenuOpenChange,\n  isSidebarOpen,\n  onDockToSidebar,\n  onClosePopover,\n  onPrevWeek,\n  onNextWeek,\n  className,\n}: CalendarEventItemProps) {\n  const { event, segmentPosition = \"full\" } = positionedEvent;\n  const color = event.color ?? \"blue\";\n  const styles = eventColorStyles[color];\n  const eventIsPast = isPastProp ?? isPast(event.end);\n  const { view, boundaryRight, headerBottom } = useCalendarPopoverBoundary();\n  const isDayView = view === \"day\";\n\n  /** Ref to the event button element, used to measure its viewport rect. */\n  const eventRef = React.useRef<HTMLDivElement>(null);\n\n  /**\n   * Viewport-relative top & height of the event element.\n   * Used to vertically align the day-view PopoverAnchor with the event\n   * so the popover appears beside the event rather than at a fixed position.\n   */\n  const [anchorRect, setAnchorRect] = React.useState<{\n    top: number;\n    height: number;\n  } | null>(null);\n\n  const showPopover = isSelected && isSidebarOpen === false;\n\n  // Measure the event element's viewport position when the popover opens in\n  // day view. useLayoutEffect ensures the measurement happens before paint so\n  // the PopoverAnchor is positioned correctly on first frame.\n  React.useLayoutEffect(() => {\n    if (!showPopover || !isDayView || !eventRef.current) {\n      setAnchorRect(null);\n      return;\n    }\n    const rect = eventRef.current.getBoundingClientRect();\n    setAnchorRect({ top: rect.top, height: rect.height });\n  }, [showPopover, isDayView]);\n\n  const hasTopRounding =\n    segmentPosition === \"start\" || segmentPosition === \"full\";\n  const hasBottomRounding =\n    segmentPosition === \"end\" || segmentPosition === \"full\";\n  const showTopResize =\n    segmentPosition === \"start\" || segmentPosition === \"full\";\n  const showBottomResize =\n    segmentPosition === \"end\" || segmentPosition === \"full\";\n\n  const [contextMenu, setContextMenu] = React.useState<{\n    x: number;\n    y: number;\n  } | null>(null);\n\n  const closeContextMenu = React.useCallback(() => {\n    setContextMenu(null);\n    onContextMenuOpenChange?.(false);\n  }, [onContextMenuOpenChange]);\n\n  const displayStart = overrideStart ?? event.start;\n  const displayEnd = overrideEnd ?? event.end;\n\n  const displayEvent: CalendarEvent =\n    overrideStart && overrideEnd\n      ? { ...event, start: displayStart, end: displayEnd }\n      : event;\n\n  const defaultStyle = {\n    top: `${positionedEvent.top}%`,\n    height: `${positionedEvent.height}%`,\n    left: `${positionedEvent.left}%`,\n    width: `${positionedEvent.width}%`,\n    minHeight: \"20px\",\n    zIndex: isSelected ? 20 : positionedEvent.column,\n  };\n\n  const posStyle =\n    overrideStart && overrideEnd\n      ? computeOverrideStyle(\n          positionedEvent,\n          hourHeight,\n          overrideStart,\n          overrideEnd,\n        )\n      : defaultStyle;\n\n  const heightInPixels =\n    overrideStart && overrideEnd\n      ? Number.parseFloat(String(posStyle.height))\n      : (positionedEvent.height / 100) * 24 * hourHeight;\n  const isCompact = heightInPixels < 40;\n\n  if (dragVariant === \"ghost\") {\n    return (\n      <div\n        className={cn(\n          \"absolute rounded-sm px-2 py-1 pointer-events-none opacity-30 overflow-hidden\",\n          className,\n        )}\n        style={{\n          top: `${positionedEvent.top}%`,\n          height: `${positionedEvent.height}%`,\n          left: `${positionedEvent.left}%`,\n          width: `${positionedEvent.width}%`,\n          minHeight: \"20px\",\n          zIndex: 15,\n        }}\n      >\n        <div className=\"absolute inset-0 rounded-sm bg-white dark:bg-[#191919]\" />\n        <div className={cn(\"absolute inset-0 rounded-sm\", styles.bg)} />\n        <div\n          className={cn(\n            \"absolute left-0 top-0 bottom-0 w-[4px] rounded-l-md dark:bg-white dark:mix-blend-overlay\",\n            styles.border,\n          )}\n        />\n        <div\n          className={cn(\n            \"relative flex flex-col h-full pl-1 overflow-hidden\",\n            isCompact && \"flex-row items-center gap-1\",\n          )}\n        >\n          <span\n            className={cn(\n              \"font-medium text-[0.625rem] leading-tight break-words\",\n              styles.text,\n              \"dark:text-white/80\",\n            )}\n          >\n            {event.title}\n          </span>\n          {!isCompact && (\n            <span\n              className={cn(\n                \"text-[0.625rem] whitespace-nowrap\",\n                styles.text,\n                \"dark:text-white dark:mix-blend-overlay\",\n              )}\n            >\n              {formatEventTimeRange(event)}\n            </span>\n          )}\n        </div>\n      </div>\n    );\n  }\n\n  if (dragVariant === \"placeholder\") {\n    return (\n      <div\n        className={cn(\n          \"absolute rounded-sm pointer-events-none border-2\",\n          styles.borderLine,\n          className,\n        )}\n        style={{\n          ...posStyle,\n          left: \"0%\",\n          width: \"100%\",\n          zIndex: 25,\n        }}\n      />\n    );\n  }\n\n  const isDraggingCopy = dragVariant === \"dragging\";\n\n  if (isDraggingCopy) {\n    const durationMinutes =\n      (displayEnd.getTime() - displayStart.getTime()) / 60000;\n    const heightPx = fixedHeight ?? (durationMinutes / 60) * hourHeight;\n\n    const useFixed = cursorX != null && cursorY != null;\n\n    const draggingStyle: React.CSSProperties = useFixed\n      ? {\n          position: \"fixed\",\n          top: `${cursorY}px`,\n          left: `${cursorX}px`,\n          height: `${heightPx}px`,\n          width: fixedWidth != null ? `${fixedWidth}px` : \"200px\",\n          minHeight: \"20px\",\n          zIndex: 30,\n        }\n      : {\n          top: posStyle.top,\n          height: `${heightPx}px`,\n          left: `${positionedEvent.left}%`,\n          width: `${positionedEvent.width}%`,\n          minHeight: \"20px\",\n          zIndex: 30,\n        };\n\n    return (\n      <div\n        tabIndex={-1}\n        className={cn(\n          \"absolute rounded-sm px-2 py-1\",\n          \"pointer-events-none cursor-grabbing\",\n          \"overflow-hidden select-none opacity-80 shadow-lg\",\n          className,\n        )}\n        style={draggingStyle}\n      >\n        <div className=\"absolute inset-0 rounded-sm bg-white dark:bg-[#191919]\" />\n        <div className={cn(\"absolute inset-0 rounded-sm\", styles.bg)} />\n        <div\n          className={cn(\n            \"absolute left-0 top-0 bottom-0 w-[4px] rounded-l-md dark:bg-white dark:mix-blend-overlay\",\n            styles.border,\n          )}\n        />\n        <div\n          className={cn(\n            \"relative flex flex-col h-full pl-1 overflow-hidden\",\n            heightPx < 40 && \"flex-row items-center gap-1\",\n          )}\n        >\n          <span className=\"font-medium text-[0.625rem] leading-tight break-words text-white dark:text-white flex items-center gap-0.5\">\n            {event.title}\n          </span>\n          {heightPx >= 40 && (\n            <span className=\"text-[0.625rem] whitespace-nowrap text-white dark:text-white\">\n              {formatEventTimeRange(displayEvent)}\n            </span>\n          )}\n        </div>\n      </div>\n    );\n  }\n\n  function handleMouseMove(e: React.MouseEvent) {\n    const target = e.currentTarget as HTMLElement;\n    const rect = target.getBoundingClientRect();\n    const offsetY = e.clientY - rect.top;\n    const height = rect.height;\n\n    if (showTopResize && showBottomResize && height < RESIZE_HOTZONE_PX * 2) {\n      target.style.cursor = \"row-resize\";\n      return;\n    }\n\n    if (showTopResize && offsetY <= RESIZE_HOTZONE_PX) {\n      target.style.cursor = \"row-resize\";\n      return;\n    }\n\n    if (showBottomResize && offsetY >= height - RESIZE_HOTZONE_PX) {\n      target.style.cursor = \"row-resize\";\n      return;\n    }\n\n    target.style.cursor = \"default\";\n  }\n\n  function handleMouseDown(e: React.MouseEvent) {\n    e.stopPropagation();\n\n    const target = e.currentTarget as HTMLElement;\n    const rect = target.getBoundingClientRect();\n    const offsetY = e.clientY - rect.top;\n    const height = rect.height;\n\n    if (showTopResize && showBottomResize && height < RESIZE_HOTZONE_PX * 2) {\n      const edge = offsetY < height / 2 ? \"top\" : \"bottom\";\n      onResizeMouseDown?.(e, event, edge);\n      return;\n    }\n\n    if (showTopResize && offsetY <= RESIZE_HOTZONE_PX) {\n      onResizeMouseDown?.(e, event, \"top\");\n      return;\n    }\n\n    if (showBottomResize && offsetY >= height - RESIZE_HOTZONE_PX) {\n      onResizeMouseDown?.(e, event, \"bottom\");\n      return;\n    }\n\n    onDragMouseDown?.(e, event);\n  }\n\n  function handleClick(e: React.MouseEvent) {\n    e.stopPropagation();\n    if (!onClick) return;\n    onClick(event);\n  }\n\n  function handleKeyDown(e: React.KeyboardEvent) {\n    if (e.key !== \"Enter\" && e.key !== \" \") return;\n    e.preventDefault();\n    onClick?.(event);\n  }\n\n  function handleContextMenu(e: React.MouseEvent) {\n    e.preventDefault();\n    e.stopPropagation();\n    setContextMenu({ x: e.clientX, y: e.clientY });\n    onContextMenuOpenChange?.(true);\n  }\n\n  const eventElement = (\n    <div\n      ref={eventRef}\n      role=\"button\"\n      tabIndex={0}\n      onMouseDown={handleMouseDown}\n      onMouseMove={handleMouseMove}\n      onClick={handleClick}\n      onKeyDown={handleKeyDown}\n      onContextMenu={handleContextMenu}\n      className={cn(\n        \"absolute px-2 py-1\",\n        hasTopRounding && \"rounded-t-md\",\n        hasBottomRounding && \"rounded-b-md\",\n        \"cursor-default hover:z-10 focus:outline-none focus-visible:outline-none\",\n        \"overflow-hidden select-none\",\n        isSelected && \"z-20\",\n        className,\n      )}\n      style={{\n        ...posStyle,\n        zIndex: isSelected ? 20 : positionedEvent.column,\n      }}\n    >\n      {/* Solid background layer to prevent transparency bleed-through */}\n      <div\n        className={cn(\n          \"absolute inset-0 bg-white dark:bg-[#191919]\",\n          hasTopRounding && \"rounded-t-md\",\n          hasBottomRounding && \"rounded-b-md\",\n        )}\n      />\n\n      {/* Colored background layer - uses border color when selected */}\n      <div\n        className={cn(\n          \"absolute inset-0\",\n          hasTopRounding && \"rounded-t-md\",\n          hasBottomRounding && \"rounded-b-md\",\n          isSelected ? styles.border : styles.bg,\n          eventIsPast && !isSelected && \"opacity-60\",\n        )}\n      />\n\n      {/* Left border - hidden when selected (merges with bg) */}\n      {!isSelected && (\n        <div\n          className={cn(\n            \"absolute left-0 top-0 bottom-0 w-[4px] dark:bg-white dark:mix-blend-overlay\",\n            hasTopRounding && \"rounded-tl-md\",\n            hasBottomRounding && \"rounded-bl-md\",\n            styles.border,\n            eventIsPast && \"opacity-60\",\n          )}\n        />\n      )}\n      <div\n        className={cn(\n          \"relative flex flex-col h-full pl-1 overflow-hidden\",\n          isCompact && \"flex-row items-center gap-1\",\n        )}\n      >\n        <span\n          className={cn(\n            \"font-medium text-[0.625rem] leading-tight break-words flex items-center gap-0.5\",\n            isSelected\n              ? \"text-white dark:text-white\"\n              : cn(\n                  styles.text,\n                  \"dark:text-white/80\",\n                  eventIsPast && \"opacity-60\",\n                ),\n          )}\n        >\n          {event.title}\n        </span>\n        {!isCompact && (\n          <span\n            className={cn(\n              \"text-[0.625rem] whitespace-nowrap\",\n              isSelected\n                ? \"text-white dark:text-white\"\n                : cn(\n                    styles.text,\n                    \"dark:text-white dark:mix-blend-overlay\",\n                    eventIsPast && \"opacity-60 dark:opacity-100\",\n                  ),\n            )}\n          >\n            {formatEventTimeRange(displayEvent)}\n          </span>\n        )}\n      </div>\n    </div>\n  );\n\n  if (showPopover) {\n    return (\n      <>\n        <Popover\n          open\n          onOpenChange={(open) => {\n            if (!open) onClosePopover?.();\n          }}\n        >\n          <PopoverTrigger asChild>{eventElement}</PopoverTrigger>\n          {/*\n           * In day view the event spans the full grid width, so Radix can't\n           * fit the popover beside the trigger. Place a zero-width anchor at\n           * the RIGHT edge of the calendar boundary and use side=\"left\" so\n           * the popover extends leftward \\u2014 matching Notion Calendar.\n           *\n           * The anchor is portaled to document.body to escape scroll\n           * containers that apply CSS transforms (which break position:fixed\n           * by creating a new containing block).\n           */}\n          {isDayView &&\n            createPortal(\n              <PopoverAnchor\n                className=\"pointer-events-none\"\n                style={{\n                  position: \"fixed\",\n                  left: boundaryRight,\n                  top: anchorRect?.top ?? 0,\n                  height: anchorRect?.height ?? 0,\n                  width: 0,\n                }}\n              />,\n              document.body,\n            )}\n          <EventDetailPopover\n            event={event}\n            onEventChange={onEventChange}\n            onClose={() => onClosePopover?.()}\n            onDockToSidebar={() => onDockToSidebar?.()}\n            onPrevWeek={onPrevWeek}\n            onNextWeek={onNextWeek}\n            side={isDayView ? \"left\" : \"right\"}\n            collisionPaddingTop={isDayView ? headerBottom : undefined}\n          />\n        </Popover>\n        {contextMenu && (\n          <EventContextMenu\n            event={event}\n            position={contextMenu}\n            onClose={closeContextMenu}\n            onEventChange={onEventChange}\n          />\n        )}\n      </>\n    );\n  }\n\n  return (\n    <>\n      {eventElement}\n      {contextMenu && (\n        <EventContextMenu\n          event={event}\n          position={contextMenu}\n          onClose={closeContextMenu}\n          onEventChange={onEventChange}\n        />\n      )}\n    </>\n  );\n}\n\n/** Drag visual variant for all-day events */\nexport type AllDayDragVariant = \"ghost\" | \"placeholder\" | \"dragging\";\n\nexport interface AllDayEventItemProps {\n  event: CalendarEvent;\n  isPast?: boolean;\n  isSelected?: boolean;\n  onClick?: (event: CalendarEvent) => void;\n  className?: string;\n  /** For multi-day events: position info */\n  spanStart?: boolean;\n  spanEnd?: boolean;\n  /** Mousedown handler to initiate horizontal resize or drag */\n  onResizeMouseDown?: (\n    e: React.MouseEvent,\n    event: CalendarEvent,\n    edge: \"left\" | \"right\" | \"move\",\n  ) => void;\n  /** Callback when an event is changed (e.g. color change from context menu) */\n  onEventChange?: (event: CalendarEvent) => void;\n  /** Callback when context menu open state changes */\n  onContextMenuOpenChange?: (open: boolean) => void;\n  /** Whether the right sidebar is open (controls popover visibility) */\n  isSidebarOpen?: boolean;\n  /** Callback to dock popover to sidebar */\n  onDockToSidebar?: () => void;\n  /** Callback to close popover (deselect event) */\n  onClosePopover?: () => void;\n  /** Navigate to previous week */\n  onPrevWeek?: () => void;\n  /** Navigate to next week */\n  onNextWeek?: () => void;\n  /**\n   * Percentage of the event's width that is hidden off-screen to the left.\n   * Used in day view to offset the title into the visible area so multi-day\n   * events always show their title \\u2014 \\u201csticky title\\u201d effect.\n   */\n  titleOffsetPercent?: number;\n  /** Visual variant during drag operations */\n  dragVariant?: AllDayDragVariant;\n}\n\n/**\n * Formats start time for all-day events like \"8:45 AM\" or \"4 PM\"\n */\nfunction formatAllDayStartTime(date: Date): string {\n  const minutes = date.getMinutes();\n  if (minutes === 0) {\n    return format(date, \"h a\");\n  }\n  return format(date, \"h:mm a\");\n}\n\nconst ALL_DAY_RESIZE_HOTZONE_PX = 6;\n\nexport function AllDayEventItem({\n  event,\n  isPast: isPastProp,\n  isSelected,\n  onClick,\n  className,\n  spanStart = true,\n  spanEnd = true,\n  onResizeMouseDown,\n  onEventChange,\n  onContextMenuOpenChange,\n  isSidebarOpen,\n  onDockToSidebar,\n  onClosePopover,\n  onPrevWeek,\n  onNextWeek,\n  titleOffsetPercent = 0,\n  dragVariant,\n}: AllDayEventItemProps) {\n  const color = event.color ?? \"blue\";\n  const styles = eventColorStyles[color];\n  const { view, boundaryRight, headerBottom } = useCalendarPopoverBoundary();\n  const isDayView = view === \"day\";\n  const eventIsPast = isPastProp ?? isPast(event.end);\n\n  const [contextMenu, setContextMenu] = React.useState<{\n    x: number;\n    y: number;\n  } | null>(null);\n\n  const closeContextMenu = React.useCallback(() => {\n    setContextMenu(null);\n    onContextMenuOpenChange?.(false);\n  }, [onContextMenuOpenChange]);\n\n  // Ghost: faded version at original position during move\n  if (dragVariant === \"ghost\") {\n    return (\n      <div\n        className={cn(\n          \"relative h-6 px-2 py-0.5 pointer-events-none opacity-30\",\n          \"overflow-hidden select-none flex items-center gap-1\",\n          spanStart && \"rounded-l-md\",\n          spanEnd && \"rounded-r-md\",\n          className,\n        )}\n      >\n        <div\n          className={cn(\n            \"absolute inset-0 bg-white dark:bg-[#191919]\",\n            spanStart && \"rounded-l-md\",\n            spanEnd && \"rounded-r-md\",\n          )}\n        />\n        <div\n          className={cn(\n            \"absolute inset-0\",\n            styles.bg,\n            spanStart && \"rounded-l-md\",\n            spanEnd && \"rounded-r-md\",\n          )}\n        />\n        {spanStart && (\n          <div\n            className={cn(\n              \"absolute left-0 top-0 bottom-0 w-[4px] dark:bg-white dark:mix-blend-overlay\",\n              spanStart && \"rounded-l-md\",\n              styles.border,\n            )}\n          />\n        )}\n        <span\n          className={cn(\n            \"relative font-medium text-[0.625rem] leading-tight whitespace-nowrap\",\n            spanStart && \"pl-1\",\n            styles.text,\n            \"dark:text-white/80\",\n          )}\n        >\n          {event.title}\n        </span>\n      </div>\n    );\n  }\n\n  // Placeholder: border-only outline at target position\n  if (dragVariant === \"placeholder\") {\n    return (\n      <div\n        className={cn(\n          \"relative h-6 pointer-events-none border-2 rounded-sm\",\n          styles.borderLine,\n          className,\n        )}\n      />\n    );\n  }\n\n  // Dragging copy: floating replica following cursor\n  if (dragVariant === \"dragging\") {\n    return (\n      <div\n        className={cn(\n          \"h-6 px-2 py-0.5 pointer-events-none cursor-grabbing\",\n          \"overflow-hidden select-none flex items-center gap-1\",\n          \"rounded-sm opacity-80 shadow-lg\",\n          className,\n        )}\n      >\n        <div className=\"absolute inset-0 rounded-sm bg-white dark:bg-[#191919]\" />\n        <div className={cn(\"absolute inset-0 rounded-sm\", styles.bg)} />\n        <div\n          className={cn(\n            \"absolute left-0 top-0 bottom-0 w-[4px] rounded-l-md dark:bg-white dark:mix-blend-overlay\",\n            styles.border,\n          )}\n        />\n        <span\n          className={cn(\n            \"relative font-medium text-[0.625rem] leading-tight whitespace-nowrap pl-1\",\n            styles.text,\n            \"dark:text-white/80\",\n          )}\n        >\n          {event.title}\n        </span>\n      </div>\n    );\n  }\n\n  // Check if event has a specific start time (not midnight)\n  const hasStartTime =\n    event.start.getHours() !== 0 || event.start.getMinutes() !== 0;\n\n  function handleClick(e: React.MouseEvent) {\n    e.stopPropagation();\n    if (!onClick) {\n      return;\n    }\n    onClick(event);\n  }\n\n  function handleKeyDown(e: React.KeyboardEvent) {\n    if (e.key !== \"Enter\" && e.key !== \" \") {\n      return;\n    }\n    e.preventDefault();\n    onClick?.(event);\n  }\n\n  function handleAllDayMouseMove(e: React.MouseEvent) {\n    const target = e.currentTarget as HTMLElement;\n    const rect = target.getBoundingClientRect();\n    const offsetX = e.clientX - rect.left;\n    const width = rect.width;\n\n    if (spanStart && offsetX <= ALL_DAY_RESIZE_HOTZONE_PX) {\n      target.style.cursor = \"col-resize\";\n      return;\n    }\n\n    if (spanEnd && offsetX >= width - ALL_DAY_RESIZE_HOTZONE_PX) {\n      target.style.cursor = \"col-resize\";\n      return;\n    }\n\n    target.style.cursor = \"default\";\n  }\n\n  function handleAllDayMouseDown(e: React.MouseEvent) {\n    if (!onResizeMouseDown) return;\n\n    const target = e.currentTarget as HTMLElement;\n    const rect = target.getBoundingClientRect();\n    const offsetX = e.clientX - rect.left;\n    const width = rect.width;\n\n    if (spanStart && offsetX <= ALL_DAY_RESIZE_HOTZONE_PX) {\n      e.stopPropagation();\n      onResizeMouseDown(e, event, \"left\");\n      return;\n    }\n\n    if (spanEnd && offsetX >= width - ALL_DAY_RESIZE_HOTZONE_PX) {\n      e.stopPropagation();\n      onResizeMouseDown(e, event, \"right\");\n      return;\n    }\n\n    // Middle area: initiate drag (move)\n    e.stopPropagation();\n    onResizeMouseDown(e, event, \"move\");\n  }\n\n  function handleContextMenu(e: React.MouseEvent) {\n    e.preventDefault();\n    e.stopPropagation();\n    setContextMenu({ x: e.clientX, y: e.clientY });\n    onContextMenuOpenChange?.(true);\n  }\n\n  const showPopover = isSelected && isSidebarOpen === false;\n\n  const eventElement = (\n    <div\n      role=\"button\"\n      tabIndex={0}\n      onClick={handleClick}\n      onKeyDown={handleKeyDown}\n      onContextMenu={handleContextMenu}\n      onMouseMove={handleAllDayMouseMove}\n      onMouseDown={handleAllDayMouseDown}\n      className={cn(\n        \"relative h-6 px-2 py-0.5 cursor-default\",\n        \"hover:z-10 focus:outline-none focus-visible:outline-none\",\n        \"overflow-hidden select-none flex items-center gap-1\",\n        spanStart && \"rounded-l-md\",\n        spanEnd && \"rounded-r-md\",\n        isSelected && \"z-20\",\n        className,\n      )}\n      style={\n        titleOffsetPercent > 0\n          ? { paddingLeft: `${titleOffsetPercent}%` }\n          : undefined\n      }\n    >\n      {/* Solid background layer to prevent transparency bleed-through */}\n      <div\n        className={cn(\n          \"absolute inset-0 bg-white dark:bg-[#191919]\",\n          spanStart && \"rounded-l-md\",\n          spanEnd && \"rounded-r-md\",\n        )}\n      />\n\n      {/* Colored background layer - uses border color when selected */}\n      <div\n        className={cn(\n          \"absolute inset-0\",\n          isSelected ? styles.border : styles.bg,\n          spanStart && \"rounded-l-md\",\n          spanEnd && \"rounded-r-md\",\n          eventIsPast && !isSelected && \"opacity-60\",\n        )}\n      />\n\n      {/* Left border - hidden when selected (merges with bg) */}\n      {spanStart && !isSelected && (\n        <div\n          className={cn(\n            \"absolute left-0 top-0 bottom-0 w-[4px] dark:bg-white dark:mix-blend-overlay\",\n            spanStart && \"rounded-l-md\",\n            styles.border,\n            eventIsPast && \"opacity-60\",\n          )}\n        />\n      )}\n      <span\n        className={cn(\n          \"relative font-medium text-[0.625rem] leading-tight whitespace-nowrap\",\n          spanStart && \"pl-1\",\n          isSelected\n            ? \"text-white dark:text-white\"\n            : cn(\n                styles.text,\n                \"dark:text-white/80\",\n                eventIsPast && \"opacity-60\",\n              ),\n        )}\n      >\n        {event.title}\n      </span>\n      {hasStartTime && (\n        <span\n          className={cn(\n            \"relative text-[0.625rem] leading-tight whitespace-nowrap shrink-0\",\n            isSelected\n              ? \"text-white dark:text-white\"\n              : cn(\n                  styles.text,\n                  \"dark:text-white dark:mix-blend-overlay\",\n                  eventIsPast && \"opacity-60\",\n                ),\n          )}\n        >\n          {formatAllDayStartTime(event.start)}\n        </span>\n      )}\n    </div>\n  );\n\n  if (showPopover) {\n    return (\n      <>\n        <Popover\n          open\n          onOpenChange={(open) => {\n            if (!open) onClosePopover?.();\n          }}\n        >\n          <PopoverTrigger asChild>{eventElement}</PopoverTrigger>\n          {/*\n           * In day view, all-day events span the full width. Portal the\n           * anchor to document.body (escaping transformed scroll containers)\n           * and position it at the calendar boundary's right edge so the\n           * popover always appears at the visible right edge \\u2014 even when the\n           * event wrapper extends into off-screen buffer days.\n           */}\n          {isDayView &&\n            createPortal(\n              <PopoverAnchor\n                className=\"pointer-events-none\"\n                style={{\n                  position: \"fixed\",\n                  left: boundaryRight,\n                  top: 0,\n                  bottom: 0,\n                  width: 0,\n                }}\n              />,\n              document.body,\n            )}\n          <EventDetailPopover\n            event={event}\n            onEventChange={onEventChange}\n            onClose={() => onClosePopover?.()}\n            onDockToSidebar={() => onDockToSidebar?.()}\n            onPrevWeek={onPrevWeek}\n            onNextWeek={onNextWeek}\n            side={isDayView ? \"left\" : \"right\"}\n            align=\"start\"\n            collisionPaddingTop={isDayView ? headerBottom : undefined}\n          />\n        </Popover>\n        {contextMenu && (\n          <EventContextMenu\n            event={event}\n            position={contextMenu}\n            onClose={closeContextMenu}\n            onEventChange={onEventChange}\n          />\n        )}\n      </>\n    );\n  }\n\n  return (\n    <>\n      {eventElement}\n      {contextMenu && (\n        <EventContextMenu\n          event={event}\n          position={contextMenu}\n          onClose={closeContextMenu}\n          onEventChange={onEventChange}\n        />\n      )}\n    </>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/calendar-event-item.tsx"
    },
    {
      "path": "components/layouts/calendar/calendar-popover-context.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ViewType } from \"./week-view-types\";\n\n/**\n * Context to provide the collision boundary element, header inset,\n * and current view type for event detail popovers.\n * - boundary: the outer calendar container (so popovers can position freely)\n * - headerHeight: the height of the weekday header + all-day row,\n *   used as top collision padding so popovers never overlap the header.\n * - view: current calendar view (\"day\" | \"week\") — in day view, the collision\n *   boundary is skipped so Radix uses the viewport for positioning.\n * - boundaryRight: the right-edge x-coordinate (in viewport px) of the\n *   calendar boundary. Used in day view to place a fixed-position popover\n *   anchor at the calendar's right edge so popovers match Notion Calendar.\n */\n\ninterface CalendarPopoverBoundaryValue {\n  boundary: HTMLElement | null;\n  headerHeight: number;\n  view: ViewType;\n  /** Right edge of the calendar boundary in viewport pixels. */\n  boundaryRight: number;\n  /**\n   * Bottom edge of the header (weekday + all-day row) in viewport pixels.\n   * Used as collision padding top in day view so the popover never overlaps\n   * the header area when the collision boundary is the viewport.\n   */\n  headerBottom: number;\n}\n\nconst CalendarPopoverBoundaryContext =\n  React.createContext<CalendarPopoverBoundaryValue>({\n    boundary: null,\n    headerHeight: 0,\n    view: \"week\",\n    boundaryRight: 0,\n    headerBottom: 0,\n  });\n\nexport function CalendarPopoverBoundaryProvider({\n  boundaryRef,\n  headerRef,\n  view = \"week\",\n  children,\n}: {\n  boundaryRef: React.RefObject<HTMLElement | null>;\n  headerRef: React.RefObject<HTMLElement | null>;\n  view?: ViewType;\n  children: React.ReactNode;\n}) {\n  const [boundary, setBoundary] = React.useState<HTMLElement | null>(null);\n  const [headerHeight, setHeaderHeight] = React.useState(0);\n  const [boundaryRight, setBoundaryRight] = React.useState(0);\n  const [headerBottom, setHeaderBottom] = React.useState(0);\n\n  React.useEffect(() => {\n    setBoundary(boundaryRef.current);\n  }, [boundaryRef]);\n\n  // Observe header height changes (all-day row can expand/collapse).\n  // Also track the viewport-relative bottom edge of the header for day-view\n  // collision padding — the popover must not overlap the header area.\n  React.useEffect(() => {\n    const el = headerRef.current;\n    if (!el) return;\n\n    const update = () => {\n      setHeaderHeight(el.offsetHeight);\n      setHeaderBottom(el.getBoundingClientRect().bottom);\n    };\n    update();\n\n    const ro = new ResizeObserver(update);\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [headerRef]);\n\n  // Track the right edge of the calendar boundary for day-view anchoring.\n  // A ResizeObserver catches layout changes; we don't need scroll since the\n  // boundary element itself doesn't scroll within the viewport.\n  React.useEffect(() => {\n    const el = boundaryRef.current;\n    if (!el) return;\n\n    const update = () => {\n      setBoundaryRight(el.getBoundingClientRect().right);\n    };\n    update();\n\n    const ro = new ResizeObserver(update);\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [boundaryRef]);\n\n  const value = React.useMemo(\n    () => ({ boundary, headerHeight, view, boundaryRight, headerBottom }),\n    [boundary, headerHeight, view, boundaryRight, headerBottom],\n  );\n\n  return (\n    <CalendarPopoverBoundaryContext.Provider value={value}>\n      {children}\n    </CalendarPopoverBoundaryContext.Provider>\n  );\n}\n\nexport function useCalendarPopoverBoundary() {\n  return React.useContext(CalendarPopoverBoundaryContext);\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/calendar-popover-context.tsx"
    },
    {
      "path": "components/layouts/calendar/calendars.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronRight, Eye, EyeOff, Rss } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type {\n  CalendarAccount,\n  CalendarColor,\n} from \"@/components/layouts/calendar/sidebar-right\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport {\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarSeparator,\n} from \"@/components/ui/sidebar\";\n\nconst colorStyles: Record<CalendarColor, string> = {\n  red: \"bg-event-red\",\n  orange: \"bg-event-orange\",\n  yellow: \"bg-event-yellow\",\n  green: \"bg-event-green\",\n  blue: \"bg-event-blue\",\n  purple: \"bg-event-purple\",\n  gray: \"bg-event-gray\",\n};\n\ninterface CalendarsProps {\n  accounts: CalendarAccount[];\n}\n\nexport function Calendars({ accounts }: CalendarsProps) {\n  const [visibleCalendars, setVisibleCalendars] = React.useState<Set<string>>(\n    () => {\n      const visible = new Set<string>();\n      for (const account of accounts) {\n        for (const calendar of account.calendars) {\n          if (calendar.visible) {\n            visible.add(`${account.email}-${calendar.name}`);\n          }\n        }\n      }\n      return visible;\n    },\n  );\n\n  const toggleVisibility = (accountEmail: string, calendarName: string) => {\n    const key = `${accountEmail}-${calendarName}`;\n    setVisibleCalendars((prev) => {\n      const next = new Set(prev);\n      if (next.has(key)) {\n        next.delete(key);\n      } else {\n        next.add(key);\n      }\n      return next;\n    });\n  };\n\n  return (\n    <>\n      {accounts.map((account, index) => (\n        <React.Fragment key={account.email}>\n          <SidebarGroup className=\"py-0\">\n            <Collapsible defaultOpen className=\"group/collapsible\">\n              <SidebarGroupLabel\n                asChild\n                className=\"group/label text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground w-full text-xs\"\n              >\n                <CollapsibleTrigger>\n                  <span className=\"truncate\">{account.email}</span>\n                  <ChevronRight className=\"ml-auto shrink-0 opacity-0 transition-transform group-hover/label:opacity-100 group-data-[state=open]/collapsible:rotate-90\" />\n                </CollapsibleTrigger>\n              </SidebarGroupLabel>\n              <CollapsibleContent>\n                <SidebarGroupContent>\n                  <SidebarMenu className=\"gap-0\">\n                    {account.calendars.map((calendar) => {\n                      const isVisible = visibleCalendars.has(\n                        `${account.email}-${calendar.name}`,\n                      );\n                      return (\n                        <SidebarMenuItem\n                          key={calendar.name}\n                          className=\"group/calendar-item\"\n                        >\n                          <SidebarMenuButton className=\"pr-1\">\n                            <div\n                              className={cn(\n                                \"size-3 shrink-0 flex items-center justify-center rounded-xs\",\n                                colorStyles[calendar.color],\n                                !isVisible && \"opacity-40\",\n                              )}\n                            >\n                              {calendar.isSubscribed && (\n                                <Rss className=\"size-2 text-white\" />\n                              )}\n                            </div>\n                            <span\n                              className={cn(\n                                \"flex-1 truncate text-xs text-sidebar-foreground\",\n                                !isVisible && \"opacity-50\",\n                              )}\n                            >\n                              {calendar.name}\n                            </span>\n                            <span\n                              role=\"button\"\n                              tabIndex={0}\n                              className=\"inline-flex size-6 shrink-0 items-center justify-center rounded-sm opacity-0 hover:bg-sidebar-accent group-hover/calendar-item:opacity-100\"\n                              onClick={(e) => {\n                                e.stopPropagation();\n                                toggleVisibility(account.email, calendar.name);\n                              }}\n                              onKeyDown={(e) => {\n                                if (e.key === \"Enter\" || e.key === \" \") {\n                                  e.preventDefault();\n                                  e.stopPropagation();\n                                  toggleVisibility(\n                                    account.email,\n                                    calendar.name,\n                                  );\n                                }\n                              }}\n                            >\n                              {isVisible ? (\n                                <Eye className=\"size-3.5 text-sidebar-muted-foreground\" />\n                              ) : (\n                                <EyeOff className=\"size-3.5 text-sidebar-muted-foreground\" />\n                              )}\n                            </span>\n                          </SidebarMenuButton>\n                        </SidebarMenuItem>\n                      );\n                    })}\n                  </SidebarMenu>\n                </SidebarGroupContent>\n              </CollapsibleContent>\n            </Collapsible>\n          </SidebarGroup>\n          {index < accounts.length - 1 && <SidebarSeparator className=\"mx-0\" />}\n        </React.Fragment>\n      ))}\n    </>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/calendars.tsx"
    },
    {
      "path": "components/layouts/calendar/command-menu.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  CommandDialog,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport { Kbd, KbdGroup } from \"@/components/ui/kbd\";\n\nimport type { ViewType } from \"@/components/layouts/calendar/week-view-types\";\n\n/** Reusable \"Soon\" badge for unimplemented command items */\nconst SOON_BADGE = (\n  <span className=\"bg-muted text-muted-foreground ml-auto rounded px-1.5 py-0.5 text-[10px] leading-none font-medium\">\n    Soon\n  </span>\n);\n\ninterface CommandMenuProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  onGoToToday: () => void;\n  onGoToPrev: () => void;\n  onGoToNext: () => void;\n  onSwitchView: (view: ViewType) => void;\n  onToggleLeftSidebar: () => void;\n  onToggleRightSidebar: () => void;\n  onCycleTheme: () => void;\n}\n\nexport function CommandMenu({\n  open,\n  onOpenChange,\n  onGoToToday,\n  onGoToPrev,\n  onGoToNext,\n  onSwitchView,\n  onToggleLeftSidebar,\n  onToggleRightSidebar,\n  onCycleTheme,\n}: CommandMenuProps) {\n  const runCommand = React.useCallback(\n    (command: () => void) => {\n      onOpenChange(false);\n      command();\n    },\n    [onOpenChange],\n  );\n\n  return (\n    <CommandDialog\n      open={open}\n      onOpenChange={onOpenChange}\n      title=\"Command Menu\"\n      description=\"Search for a command to run...\"\n    >\n      <CommandInput placeholder=\"Type a command...\" />\n      <CommandList>\n        <CommandEmpty>No results found.</CommandEmpty>\n\n        {/* ── Calendar ── */}\n        <CommandGroup heading=\"Calendar\">\n          <CommandItem disabled>\n            Create event...\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Meet with...\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Show teammate calendar...\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Create recurring scheduling link...\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Create one-off scheduling link...\n            {SOON_BADGE}\n          </CommandItem>\n        </CommandGroup>\n\n        {/* ── Navigation ── */}\n        <CommandGroup heading=\"Navigation\">\n          <CommandItem disabled>\n            Go to date...\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem onSelect={() => runCommand(onGoToToday)}>\n            Go to today\n            <Kbd className=\"ml-auto\">T</Kbd>\n          </CommandItem>\n          <CommandItem onSelect={() => runCommand(onGoToNext)}>\n            Go to next week\n            <Kbd className=\"ml-auto\">J</Kbd>\n          </CommandItem>\n          <CommandItem onSelect={() => runCommand(onGoToPrev)}>\n            Go to previous week\n            <Kbd className=\"ml-auto\">K</Kbd>\n          </CommandItem>\n          <CommandItem disabled>\n            Search events\n            {SOON_BADGE}\n          </CommandItem>\n        </CommandGroup>\n\n        {/* ── Time zones ── */}\n        <CommandGroup heading=\"Time zones\">\n          <CommandItem disabled>\n            Travel to time zone...\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Show additional time zones...\n            {SOON_BADGE}\n          </CommandItem>\n        </CommandGroup>\n\n        {/* ── App ── */}\n        <CommandGroup heading=\"App\">\n          <CommandItem onSelect={() => runCommand(onToggleLeftSidebar)}>\n            Toggle sidebar\n            <KbdGroup className=\"ml-auto\">\n              <Kbd>⌘</Kbd>\n              <Kbd>/</Kbd>\n            </KbdGroup>\n          </CommandItem>\n          <CommandItem onSelect={() => runCommand(onCycleTheme)}>\n            Set theme...\n            <KbdGroup className=\"ml-auto\">\n              <Kbd>⌘</Kbd>\n              <Kbd>⇧</Kbd>\n              <Kbd>L</Kbd>\n            </KbdGroup>\n          </CommandItem>\n        </CommandGroup>\n\n        {/* ── View ── */}\n        <CommandGroup heading=\"View\">\n          <CommandItem disabled>\n            Start week on...\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem onSelect={() => runCommand(() => onSwitchView(\"day\"))}>\n            Display day view\n            <KbdGroup className=\"ml-auto\">\n              <Kbd>D</Kbd>\n            </KbdGroup>\n          </CommandItem>\n          <CommandItem onSelect={() => runCommand(() => onSwitchView(\"week\"))}>\n            Display week view\n            <KbdGroup className=\"ml-auto\">\n              <Kbd>W</Kbd>\n            </KbdGroup>\n          </CommandItem>\n          <CommandItem onSelect={() => runCommand(() => onSwitchView(\"month\"))}>\n            Display month view\n            <KbdGroup className=\"ml-auto\">\n              <Kbd>M</Kbd>\n            </KbdGroup>\n          </CommandItem>\n          <CommandItem disabled>\n            Set number of displayed days...\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Select all visible\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Default hour size\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Zoom hours in\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Zoom hours out\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Hide weekends\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Hide declined events\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Hide week numbers\n            {SOON_BADGE}\n          </CommandItem>\n        </CommandGroup>\n\n        {/* ── Settings & help ── */}\n        <CommandGroup heading=\"Settings & help\">\n          <CommandItem disabled>\n            Get CalendarCN mobile app\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Show keyboard shortcuts\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Go to settings\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Support & feedback\n            {SOON_BADGE}\n          </CommandItem>\n        </CommandGroup>\n\n        {/* ── Accounts ── */}\n        <CommandGroup heading=\"Accounts\">\n          <CommandItem disabled>\n            Add Google Calendar account\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Manage calendar accounts\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            Log out\n            {SOON_BADGE}\n          </CommandItem>\n        </CommandGroup>\n\n        {/* ── CalendarCN ── */}\n        <CommandGroup heading=\"CalendarCN\">\n          <CommandItem disabled>\n            Check for update\n            {SOON_BADGE}\n          </CommandItem>\n          <CommandItem disabled>\n            About CalendarCN\n            {SOON_BADGE}\n          </CommandItem>\n        </CommandGroup>\n\n        {/* ── Panels ── */}\n        <CommandGroup heading=\"Panels\">\n          <CommandItem onSelect={() => runCommand(onToggleRightSidebar)}>\n            Toggle context panel\n            <Kbd className=\"ml-auto\">/</Kbd>\n          </CommandItem>\n        </CommandGroup>\n      </CommandList>\n\n      <div className=\"border-t px-3 py-2 flex items-center gap-4 text-xs text-muted-foreground\">\n        <span className=\"flex items-center gap-1\">\n          <span className=\"text-muted-foreground/70\">↑↓</span> Navigate\n        </span>\n        <span className=\"flex items-center gap-1\">\n          <span className=\"text-muted-foreground/70\">↵</span> Select\n        </span>\n        <span className=\"flex items-center gap-1\">\n          <Kbd>⎋</Kbd> Close\n        </span>\n      </div>\n    </CommandDialog>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/command-menu.tsx"
    },
    {
      "path": "components/layouts/calendar/date-picker.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { isSameDay } from \"date-fns\";\n\nimport { Calendar } from \"@/components/ui/calendar\";\nimport { SidebarGroup, SidebarGroupContent } from \"@/components/ui/sidebar\";\n\ninterface DatePickerProps {\n  onDateSelect?: (date: Date) => void;\n  currentDate?: Date;\n  visibleDays?: Date[];\n}\n\nexport function DatePicker({\n  onDateSelect,\n  currentDate,\n  visibleDays,\n}: DatePickerProps) {\n  const [today] = React.useState(() => new Date());\n  const [displayedMonth, setDisplayedMonth] = React.useState<Date>(\n    currentDate ?? today,\n  );\n\n  // Track previous first visible day to determine scroll direction\n  const prevFirstDayRef = React.useRef<Date | null>(null);\n\n  // Auto-navigate datepicker month so highlighted days stay visible\n  // Scrolling forward → keep last visible day's month shown\n  // Scrolling backward → keep first visible day's month shown\n  React.useEffect(() => {\n    if (!visibleDays || visibleDays.length === 0) return;\n\n    const firstDay = visibleDays[0];\n    const lastDay = visibleDays[visibleDays.length - 1];\n    const prevFirstDay = prevFirstDayRef.current;\n    prevFirstDayRef.current = firstDay;\n\n    const setMonthIfChanged = (anchor: Date) => {\n      setDisplayedMonth((prev) => {\n        if (\n          prev.getMonth() === anchor.getMonth() &&\n          prev.getFullYear() === anchor.getFullYear()\n        ) {\n          return prev;\n        }\n        return anchor;\n      });\n    };\n\n    // Scrolling forward → ensure last visible day's month is displayed\n    if (prevFirstDay && firstDay.getTime() > prevFirstDay.getTime()) {\n      setMonthIfChanged(lastDay);\n      return;\n    }\n\n    // Scrolling backward or initial render → ensure first visible day's month is displayed\n    setMonthIfChanged(firstDay);\n  }, [visibleDays]);\n\n  const isSameMonth =\n    displayedMonth.getMonth() === today.getMonth() &&\n    displayedMonth.getFullYear() === today.getFullYear();\n\n  const monthYearLabel = displayedMonth.toLocaleDateString(\"default\", {\n    month: \"long\",\n    year: \"numeric\",\n  });\n\n  const goBackToToday = () => {\n    setDisplayedMonth(today);\n    onDateSelect?.(today);\n  };\n\n  // Build modifiers for visible days highlighting\n  const modifiers = React.useMemo(() => {\n    if (!visibleDays || visibleDays.length === 0) return undefined;\n\n    return {\n      inView: (date: Date) => visibleDays.some((d) => isSameDay(d, date)),\n    };\n  }, [visibleDays]);\n\n  const modifiersClassNames = React.useMemo(() => {\n    if (!modifiers) return undefined;\n    return {\n      inView: \"in-view-day\",\n    };\n  }, [modifiers]);\n\n  return (\n    // Pin the mini calendar to the top of the sidebar scroll container so\n    // it stays visible while the sections below (Scheduling, accounts,\n    // teams) scroll underneath.\n    <SidebarGroup className=\"sticky top-0 z-10 bg-sidebar px-0\">\n      <SidebarGroupContent>\n        <Calendar\n          mode=\"single\"\n          month={displayedMonth}\n          onMonthChange={setDisplayedMonth}\n          selected={today}\n          onSelect={(date) => {\n            if (date) {\n              onDateSelect?.(date);\n            }\n          }}\n          fixedWeeks\n          modifiers={modifiers}\n          modifiersClassNames={modifiersClassNames}\n          // Dropping showWeekNumber: react-day-picker v8 adds the week column\n          // only to tbody rows, not to the header row, so headers and data\n          // were off-by-one — Saturday was shoved off the right edge. A\n          // clean 7-column grid (7 × 32px + padding ≈ 240px) fits the\n          // ~256px sidebar without clipping.\n          className=\"bg-transparent [&_[role=gridcell].bg-accent]:bg-sidebar-primary [&_[role=gridcell].bg-accent]:text-sidebar-primary-foreground\"\n        />\n      </SidebarGroupContent>\n    </SidebarGroup>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/date-picker.tsx"
    },
    {
      "path": "components/layouts/calendar/event-context-menu.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n  Check,\n  Copy,\n  Monitor,\n  SquareDashed,\n  TabletSmartphone,\n  Trash2,\n} from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { CalendarEvent, EventColor } from \"./week-view-types\";\n\nconst EVENT_COLORS: EventColor[] = [\n  \"red\",\n  \"orange\",\n  \"yellow\",\n  \"green\",\n  \"blue\",\n  \"purple\",\n  \"gray\",\n];\n\nconst colorSwatchClass: Record<EventColor, string> = {\n  red: \"bg-event-red-border\",\n  orange: \"bg-event-orange-border\",\n  yellow: \"bg-event-yellow-border\",\n  green: \"bg-event-green-border\",\n  blue: \"bg-event-blue-border\",\n  purple: \"bg-event-purple-border\",\n  gray: \"bg-event-gray-border\",\n};\n\ninterface CalendarAccountData {\n  email: string;\n  calendars: { name: string; color: EventColor }[];\n}\n\nconst CALENDAR_ACCOUNTS: CalendarAccountData[] = [\n  {\n    email: \"you@example.com\",\n    calendars: [\n      { name: \"you@example.com\", color: \"red\" },\n      { name: \"Personal\", color: \"purple\" },\n      { name: \"Work\", color: \"blue\" },\n      { name: \"Family\", color: \"orange\" },\n      { name: \"Side Projects\", color: \"yellow\" },\n      { name: \"Fitness\", color: \"green\" },\n      { name: \"Holidays in Brazil\", color: \"green\" },\n    ],\n  },\n];\n\ninterface EventContextMenuProps {\n  event: CalendarEvent;\n  position: { x: number; y: number };\n  onClose: () => void;\n  onEventChange?: (event: CalendarEvent) => void;\n}\n\nfunction MenuItem({\n  className,\n  children,\n  onSelect,\n}: {\n  className?: string;\n  children: React.ReactNode;\n  onSelect?: () => void;\n}) {\n  return (\n    <button\n      type=\"button\"\n      className={cn(\n        \"relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-xs outline-none select-none\",\n        \"text-white hover:bg-[#303030] focus:bg-[#303030]\",\n        className,\n      )}\n      onClick={onSelect}\n    >\n      {children}\n    </button>\n  );\n}\n\nfunction Shortcut({ children }: { children: React.ReactNode }) {\n  return <span className=\"ml-auto text-xs text-white/40\">{children}</span>;\n}\n\nfunction Separator() {\n  return <div className=\"-mx-1 my-1 h-px bg-[#303030]\" />;\n}\n\nfunction SubMenu({\n  trigger,\n  children,\n}: {\n  trigger: React.ReactNode;\n  children: React.ReactNode;\n}) {\n  const [open, setOpen] = React.useState(false);\n  const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  function handleMouseEnter() {\n    if (timeoutRef.current) clearTimeout(timeoutRef.current);\n    setOpen(true);\n  }\n\n  function handleMouseLeave() {\n    timeoutRef.current = setTimeout(() => setOpen(false), 150);\n  }\n\n  return (\n    <div\n      className=\"relative\"\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n    >\n      <button\n        type=\"button\"\n        className={cn(\n          \"relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-xs outline-none select-none\",\n          \"text-white hover:bg-[#303030] focus:bg-[#303030]\",\n          open && \"bg-[#303030]\",\n        )}\n      >\n        {trigger}\n        <svg\n          className=\"ml-auto size-4 text-white/60\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        >\n          <path d=\"m9 18 6-6-6-6\" />\n        </svg>\n      </button>\n      {open && (\n        <div className=\"absolute left-full top-0 ml-1 min-w-[180px] rounded-sm border border-[#303030] bg-[#252525] p-1 shadow-lg\">\n          {children}\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport function EventContextMenu({\n  event,\n  position,\n  onClose,\n  onEventChange,\n}: EventContextMenuProps) {\n  const menuRef = React.useRef<HTMLDivElement>(null);\n  const [adjustedPos, setAdjustedPos] = React.useState(position);\n  const [ready, setReady] = React.useState(false);\n  const currentColor = event.color ?? \"blue\";\n\n  React.useLayoutEffect(() => {\n    const menu = menuRef.current;\n    if (!menu) return;\n\n    const menuHeight = menu.offsetHeight;\n    const menuWidth = menu.offsetWidth;\n    let y = position.y;\n    let x = position.x;\n\n    if (position.y + menuHeight > window.innerHeight) {\n      y = position.y - menuHeight;\n    }\n    if (position.x + menuWidth > window.innerWidth) {\n      x = position.x - menuWidth;\n    }\n\n    setAdjustedPos({ x, y });\n    setReady(true);\n  }, [position]);\n\n  React.useEffect(() => {\n    function handleClickOutside(e: MouseEvent) {\n      if (!menuRef.current) return;\n      if (menuRef.current.contains(e.target as Node)) return;\n      onClose();\n    }\n\n    function handleEscape(e: KeyboardEvent) {\n      if (e.key !== \"Escape\") return;\n      onClose();\n    }\n\n    // Use capture to close before other handlers fire\n    document.addEventListener(\"mousedown\", handleClickOutside, true);\n    document.addEventListener(\"keydown\", handleEscape);\n    return () => {\n      document.removeEventListener(\"mousedown\", handleClickOutside, true);\n      document.removeEventListener(\"keydown\", handleEscape);\n    };\n  }, [onClose]);\n\n  function handleColorSelect(color: EventColor) {\n    onEventChange?.({ ...event, color });\n    onClose();\n  }\n\n  function handleCalendarSelect(calendarName: string) {\n    onEventChange?.({ ...event, calendarId: calendarName });\n    onClose();\n  }\n\n  return createPortal(\n    <div\n      ref={menuRef}\n      className=\"fixed z-50 min-w-[200px] rounded-sm border border-[#303030] bg-[#252525] p-1 shadow-md animate-in fade-in-0 zoom-in-95\"\n      style={{\n        top: adjustedPos.y,\n        left: adjustedPos.x,\n        opacity: ready ? 1 : 0,\n      }}\n    >\n      {/* Color selector row */}\n      <div className=\"flex items-center gap-1.5 px-2 py-1.5\">\n        {EVENT_COLORS.map((color) => (\n          <button\n            key={color}\n            type=\"button\"\n            className={cn(\n              \"relative flex size-3 items-center justify-center rounded-xs\",\n              colorSwatchClass[color],\n            )}\n            onClick={() => handleColorSelect(color)}\n          >\n            {color === currentColor && <Check className=\"size-2 text-white\" />}\n          </button>\n        ))}\n      </div>\n\n      <Separator />\n\n      {/* Block on calendar */}\n      <SubMenu\n        trigger={\n          <>\n            <Monitor className=\"size-3.5\" />\n            Block on calendar\n          </>\n        }\n      >\n        {CALENDAR_ACCOUNTS.map((account) => (\n          <React.Fragment key={account.email}>\n            <div className=\"px-2 py-1 text-[10px] text-white/40\">\n              {account.email}\n            </div>\n            {account.calendars.map((cal) => (\n              <MenuItem\n                key={cal.name}\n                onSelect={() => handleCalendarSelect(cal.name)}\n              >\n                <div\n                  className={cn(\n                    \"size-3 rounded-xs shrink-0\",\n                    colorSwatchClass[cal.color],\n                  )}\n                />\n                {cal.name}\n              </MenuItem>\n            ))}\n          </React.Fragment>\n        ))}\n      </SubMenu>\n\n      <Separator />\n\n      {/* Cut / Copy / Duplicate */}\n      <MenuItem>\n        <SquareDashed className=\"size-3.5\" />\n        Cut\n        <Shortcut>⌘X</Shortcut>\n      </MenuItem>\n      <MenuItem>\n        <TabletSmartphone className=\"size-3.5\" />\n        Copy\n        <Shortcut>⌘C</Shortcut>\n      </MenuItem>\n      <MenuItem>\n        <Copy className=\"size-3.5\" />\n        Duplicate\n        <Shortcut>⌘D</Shortcut>\n      </MenuItem>\n\n      <Separator />\n\n      {/* Delete */}\n      <MenuItem className=\"text-[#E56458] hover:!bg-[#DE5551] hover:!text-white focus:!bg-[#DE5551] focus:!text-white [&:hover>svg]:!text-white [&:focus>svg]:!text-white [&:hover>.ml-auto]:!text-white [&:focus>.ml-auto]:!text-white\">\n        <Trash2 className=\"size-3.5 text-[#E56458]\" />\n        Delete\n        <Shortcut>delete</Shortcut>\n      </MenuItem>\n    </div>,\n    document.body,\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/event-context-menu.tsx"
    },
    {
      "path": "components/layouts/calendar/event-detail-panel.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\nimport {\n  addDays,\n  differenceInCalendarDays,\n  differenceInMinutes,\n  format,\n  parse,\n} from \"date-fns\";\nimport {\n  Bell,\n  Check,\n  ChevronDown,\n  CircleHelp,\n  ChevronLeft,\n  ChevronRight,\n  Clock,\n  Copy,\n  Globe,\n  MapPin,\n  MoreHorizontal,\n  NotepadText,\n  RefreshCcw,\n  SquareDashed,\n  TabletSmartphone,\n  Trash2,\n  User,\n  Video,\n} from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Switch } from \"@/components/ui/switch\";\nimport type { CalendarEvent, EventColor } from \"./week-view-types\";\n\ninterface EventDetailPanelProps {\n  event: CalendarEvent;\n  onEventChange?: (event: CalendarEvent) => void;\n  onPrevWeek?: () => void;\n  onNextWeek?: () => void;\n  /** Extra action buttons rendered in the header row (after the \"...\" menu). */\n  headerActions?: React.ReactNode;\n}\n\nconst colorDotClass: Record<EventColor, string> = {\n  red: \"bg-event-red-border\",\n  orange: \"bg-event-orange-border\",\n  yellow: \"bg-event-yellow-border\",\n  green: \"bg-event-green-border\",\n  blue: \"bg-event-blue-border\",\n  purple: \"bg-event-purple-border\",\n  gray: \"bg-event-gray-border\",\n};\n\nfunction formatDuration(start: Date, end: Date): string {\n  const totalMinutes = differenceInMinutes(end, start);\n\n  if (totalMinutes < 60) {\n    return `${totalMinutes}min`;\n  }\n\n  const hours = Math.floor(totalMinutes / 60);\n  const minutes = totalMinutes % 60;\n\n  if (minutes === 0) {\n    return `${hours}h`;\n  }\n\n  return `${hours}h ${minutes}min`;\n}\n\nfunction formatTimeDisplay(date: Date): string {\n  const minutes = date.getMinutes();\n  if (minutes === 0) {\n    return format(date, \"h a\");\n  }\n  return format(date, \"h:mm a\");\n}\n\ninterface ParsedTime {\n  hours: number;\n  minutes: number;\n}\n\n/**\n * Parses a user-typed time string into hours and minutes.\n * Accepts formats: \"3 PM\", \"3:30 PM\", \"15:00\", \"3pm\", \"330pm\", \"3:30pm\".\n * Returns null if the input cannot be parsed.\n */\nfunction parseTimeInput(input: string): ParsedTime | null {\n  const trimmed = input.trim().toLowerCase();\n  if (trimmed.length === 0) {\n    return null;\n  }\n\n  const isPM = /pm$/.test(trimmed);\n  const isAM = /am$/.test(trimmed);\n  const stripped = trimmed.replace(/\\s*(am|pm)\\s*$/, \"\").trim();\n\n  if (stripped.length === 0) {\n    return null;\n  }\n\n  let hours: number;\n  let minutes: number;\n\n  if (stripped.includes(\":\")) {\n    const parts = stripped.split(\":\");\n    if (parts.length !== 2) {\n      return null;\n    }\n    hours = Number.parseInt(parts[0], 10);\n    minutes = Number.parseInt(parts[1], 10);\n  } else {\n    const num = Number.parseInt(stripped, 10);\n    if (Number.isNaN(num)) {\n      return null;\n    }\n    if (stripped.length > 2 && num > 99) {\n      // e.g., \"330\" → 3:30, \"1230\" → 12:30\n      minutes = num % 100;\n      hours = Math.floor(num / 100);\n    } else {\n      hours = num;\n      minutes = 0;\n    }\n  }\n\n  if (Number.isNaN(hours) || Number.isNaN(minutes)) {\n    return null;\n  }\n\n  // Apply AM/PM conversion\n  if (isPM && hours < 12) {\n    hours += 12;\n  }\n  if (isAM && hours === 12) {\n    hours = 0;\n  }\n\n  if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {\n    return null;\n  }\n\n  return { hours, minutes };\n}\n\n/**\n * Returns a new Date with the same year/month/day as `base`\n * but with hours and minutes replaced.\n */\nfunction applyTimeToDate(base: Date, hours: number, minutes: number): Date {\n  const result = new Date(base);\n  result.setHours(hours, minutes, 0, 0);\n  return result;\n}\n\nfunction formatDateDisplay(date: Date): string {\n  return format(date, \"EEE MMM d\");\n}\n\n/**\n * Parses a user-typed date string into a Date.\n * Strips any leading weekday name and parses \"MMM d\" (e.g., \"Mar 11\").\n * Uses the reference date's year. Returns null if unparseable.\n */\nfunction parseDateInput(input: string, referenceDate: Date): Date | null {\n  const trimmed = input.trim();\n  if (trimmed.length === 0) {\n    return null;\n  }\n\n  // Strip optional leading weekday (e.g., \"Tue \", \"Wed \")\n  const withoutWeekday = trimmed.replace(/^[a-z]{3}\\s+/i, \"\");\n  if (withoutWeekday.length === 0) {\n    return null;\n  }\n\n  const parsed = parse(withoutWeekday, \"MMM d\", referenceDate);\n  if (Number.isNaN(parsed.getTime())) {\n    return null;\n  }\n\n  return parsed;\n}\n\nfunction formatVisibility(\n  visibility?: \"default\" | \"public\" | \"private\",\n): string {\n  if (visibility === \"public\") {\n    return \"Public\";\n  }\n  if (visibility === \"private\") {\n    return \"Private\";\n  }\n  return \"Default visibility\";\n}\n\nfunction FieldRow({\n  icon: Icon,\n  label,\n  value,\n}: {\n  icon: React.ComponentType<{ className?: string }>;\n  label: string;\n  value?: string;\n}) {\n  return (\n    <div className=\"flex items-center gap-3 py-2\">\n      <Icon className=\"size-4 shrink-0 text-[#C7C5C1] dark:text-[#595959]\" />\n      <span className=\"text-xs text-[#C7C5C1] dark:text-[#595959]\">\n        {label}\n      </span>\n      {value && <span className=\"text-muted-foreground text-xs\">{value}</span>}\n    </div>\n  );\n}\n\nfunction TimezoneDisplay({ timezone }: { timezone: string }) {\n  const spaceIdx = timezone.indexOf(\" \");\n  if (spaceIdx === -1) {\n    return (\n      <span className=\"text-xs text-[#C7C5C1] dark:text-[#595959]\">\n        {timezone}\n      </span>\n    );\n  }\n\n  const code = timezone.substring(0, spaceIdx);\n  const city = timezone.substring(spaceIdx + 1);\n\n  return (\n    <span className=\"inline-flex items-center gap-1.5 text-xs\">\n      <span className=\"text-[#C7C5C1] dark:text-[#595959]\">{code}</span>\n      <span className=\"text-foreground\">{city}</span>\n    </span>\n  );\n}\n\nfunction RecurrenceDisplay({ recurrence }: { recurrence: string }) {\n  const onIdx = recurrence.indexOf(\" on \");\n  if (onIdx === -1) {\n    return <span className=\"text-foreground text-xs\">{recurrence}</span>;\n  }\n\n  const main = recurrence.substring(0, onIdx);\n  const suffix = recurrence.substring(onIdx);\n\n  return (\n    <span className=\"text-xs\">\n      <span className=\"text-foreground\">{main}</span>\n      <span className=\"text-[#C7C5C1] dark:text-[#595959]\">{suffix}</span>\n    </span>\n  );\n}\n\nconst EVENT_TYPES = [\n  \"Event\",\n  \"Focus time\",\n  \"Out of office\",\n  \"Birthday\",\n] as const;\ntype EventType = (typeof EVENT_TYPES)[number];\n\nconst EVENT_TYPE_TOOLTIPS: Partial<Record<EventType, string>> = {\n  \"Focus time\":\n    \"Create a focus time event with the option to automatically decline meetings during this time. Available for work and school accounts.\",\n  \"Out of office\":\n    \"Create an out of office (OOO) event with the option to automatically decline meetings during this time. Available for work and school accounts.\",\n  Birthday:\n    \"Create a birthday event to keep track of a person's upcoming birthdays. Birthdays from your Google Contacts may appear on a separate Birthday calendar.\",\n};\n\nfunction EventTypeHelpIcon({ tooltip }: { tooltip?: string }) {\n  const [tooltipPos, setTooltipPos] = React.useState<{\n    top: number;\n    left: number;\n  } | null>(null);\n\n  if (!tooltip) {\n    return null;\n  }\n\n  function handleMouseEnter(e: React.MouseEvent<HTMLSpanElement>) {\n    const rect = e.currentTarget.getBoundingClientRect();\n    setTooltipPos({ top: rect.top + rect.height / 2, left: rect.left });\n  }\n\n  function handleMouseLeave() {\n    setTooltipPos(null);\n  }\n\n  return (\n    <span\n      className=\"ml-auto shrink-0 opacity-0 group-hover/item:opacity-100 transition-opacity\"\n      onClick={(e) => e.stopPropagation()}\n      onPointerDown={(e) => e.stopPropagation()}\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n    >\n      <CircleHelp className=\"size-3.5 text-white\" />\n      {tooltipPos &&\n        ReactDOM.createPortal(\n          <div\n            className=\"pointer-events-none fixed z-[100] max-w-[240px] rounded-sm bg-[#252525] border border-[#303030] px-2 py-1 text-xs text-white shadow-md\"\n            style={{\n              top: tooltipPos.top,\n              left: tooltipPos.left - 8,\n              transform: \"translate(-100%, -50%)\",\n            }}\n          >\n            {tooltip}\n          </div>,\n          document.body,\n        )}\n    </span>\n  );\n}\n\nexport function EventDetailPanel({\n  event,\n  onEventChange,\n  onPrevWeek,\n  onNextWeek,\n  headerActions,\n}: EventDetailPanelProps) {\n  const color = event.color ?? \"blue\";\n  const [eventType, setEventType] = React.useState<EventType>(\"Event\");\n  const [eventDropdownOpen, setEventDropdownOpen] = React.useState(false);\n  const [hoveredOther, setHoveredOther] = React.useState(false);\n  /** Whether the compact \"All-day / Time zone / Repeat\" row is expanded into individual rows. */\n  const [optionsExpanded, setOptionsExpanded] = React.useState(false);\n  const [titleValue, setTitleValue] = React.useState(event.title);\n  const titleRef = React.useRef<HTMLInputElement>(null);\n  const escapePressedRef = React.useRef(false);\n  /** Stores the title when the input gains focus, used to restore on Escape. */\n  const titleOnFocusRef = React.useRef(event.title);\n\n  React.useEffect(() => {\n    setTitleValue(event.title);\n  }, [event.title]);\n\n  // Reset expanded options when switching to a different event\n  React.useEffect(() => {\n    setOptionsExpanded(false);\n  }, [event.id]);\n\n  const handleTitleChange = React.useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const next = e.target.value;\n      setTitleValue(next);\n      onEventChange?.({ ...event, title: next });\n    },\n    [event, onEventChange],\n  );\n\n  const handleTitleFocus = React.useCallback(() => {\n    titleOnFocusRef.current = event.title;\n  }, [event.title]);\n\n  const commitTitle = React.useCallback(() => {\n    if (escapePressedRef.current) {\n      escapePressedRef.current = false;\n      return;\n    }\n    const trimmed = titleValue.trim();\n    if (trimmed === titleValue) {\n      return;\n    }\n    onEventChange?.({ ...event, title: trimmed });\n  }, [titleValue, event, onEventChange]);\n\n  const handleTitleKeyDown = React.useCallback(\n    (e: React.KeyboardEvent<HTMLInputElement>) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        titleRef.current?.blur();\n        return;\n      }\n      if (e.key === \"Escape\") {\n        e.preventDefault();\n        e.stopPropagation();\n        escapePressedRef.current = true;\n        const original = titleOnFocusRef.current;\n        setTitleValue(original);\n        onEventChange?.({ ...event, title: original });\n        titleRef.current?.blur();\n      }\n    },\n    [event, onEventChange],\n  );\n\n  // --- Start time input state & handlers ---\n  const [startTimeValue, setStartTimeValue] = React.useState(() =>\n    formatTimeDisplay(event.start),\n  );\n  const startTimeRef = React.useRef<HTMLInputElement>(null);\n  const startTimeEscapePressedRef = React.useRef(false);\n  const startTimeOnFocusRef = React.useRef(formatTimeDisplay(event.start));\n\n  React.useEffect(() => {\n    setStartTimeValue(formatTimeDisplay(event.start));\n  }, [event.start]);\n\n  const handleStartTimeChange = React.useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      setStartTimeValue(e.target.value);\n    },\n    [],\n  );\n\n  const handleStartTimeFocus = React.useCallback(() => {\n    startTimeOnFocusRef.current = formatTimeDisplay(event.start);\n    requestAnimationFrame(() => {\n      startTimeRef.current?.select();\n    });\n  }, [event.start]);\n\n  const commitStartTime = React.useCallback(() => {\n    if (startTimeEscapePressedRef.current) {\n      startTimeEscapePressedRef.current = false;\n      return;\n    }\n    const parsed = parseTimeInput(startTimeValue);\n    if (!parsed) {\n      setStartTimeValue(startTimeOnFocusRef.current);\n      return;\n    }\n    const newStart = applyTimeToDate(event.start, parsed.hours, parsed.minutes);\n    if (newStart.getTime() >= event.end.getTime()) {\n      setStartTimeValue(startTimeOnFocusRef.current);\n      return;\n    }\n    setStartTimeValue(formatTimeDisplay(newStart));\n    onEventChange?.({ ...event, start: newStart });\n  }, [startTimeValue, event, onEventChange]);\n\n  const handleStartTimeKeyDown = React.useCallback(\n    (e: React.KeyboardEvent<HTMLInputElement>) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        startTimeRef.current?.blur();\n        return;\n      }\n      if (e.key === \"Escape\") {\n        e.preventDefault();\n        e.stopPropagation();\n        startTimeEscapePressedRef.current = true;\n        setStartTimeValue(startTimeOnFocusRef.current);\n        startTimeRef.current?.blur();\n      }\n    },\n    [],\n  );\n\n  // --- End time input state & handlers ---\n  const [endTimeValue, setEndTimeValue] = React.useState(() =>\n    formatTimeDisplay(event.end),\n  );\n  const endTimeRef = React.useRef<HTMLInputElement>(null);\n  const endTimeEscapePressedRef = React.useRef(false);\n  const endTimeOnFocusRef = React.useRef(formatTimeDisplay(event.end));\n\n  React.useEffect(() => {\n    setEndTimeValue(formatTimeDisplay(event.end));\n  }, [event.end]);\n\n  const handleEndTimeChange = React.useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      setEndTimeValue(e.target.value);\n    },\n    [],\n  );\n\n  const handleEndTimeFocus = React.useCallback(() => {\n    endTimeOnFocusRef.current = formatTimeDisplay(event.end);\n    requestAnimationFrame(() => {\n      endTimeRef.current?.select();\n    });\n  }, [event.end]);\n\n  const commitEndTime = React.useCallback(() => {\n    if (endTimeEscapePressedRef.current) {\n      endTimeEscapePressedRef.current = false;\n      return;\n    }\n    const parsed = parseTimeInput(endTimeValue);\n    if (!parsed) {\n      setEndTimeValue(endTimeOnFocusRef.current);\n      return;\n    }\n    const newEnd = applyTimeToDate(event.end, parsed.hours, parsed.minutes);\n    if (newEnd.getTime() <= event.start.getTime()) {\n      setEndTimeValue(endTimeOnFocusRef.current);\n      return;\n    }\n    setEndTimeValue(formatTimeDisplay(newEnd));\n    onEventChange?.({ ...event, end: newEnd });\n  }, [endTimeValue, event, onEventChange]);\n\n  const handleEndTimeKeyDown = React.useCallback(\n    (e: React.KeyboardEvent<HTMLInputElement>) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        endTimeRef.current?.blur();\n        return;\n      }\n      if (e.key === \"Escape\") {\n        e.preventDefault();\n        e.stopPropagation();\n        endTimeEscapePressedRef.current = true;\n        setEndTimeValue(endTimeOnFocusRef.current);\n        endTimeRef.current?.blur();\n      }\n    },\n    [],\n  );\n\n  // --- Date input state & handlers ---\n  const [dateValue, setDateValue] = React.useState(() =>\n    formatDateDisplay(event.start),\n  );\n  const dateRef = React.useRef<HTMLInputElement>(null);\n  const dateEscapePressedRef = React.useRef(false);\n  const dateOnFocusRef = React.useRef(formatDateDisplay(event.start));\n\n  React.useEffect(() => {\n    setDateValue(formatDateDisplay(event.start));\n  }, [event.start]);\n\n  const handleDateChange = React.useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      setDateValue(e.target.value);\n    },\n    [],\n  );\n\n  const handleDateFocus = React.useCallback(() => {\n    dateOnFocusRef.current = formatDateDisplay(event.start);\n    requestAnimationFrame(() => {\n      dateRef.current?.select();\n    });\n  }, [event.start]);\n\n  const commitDate = React.useCallback(() => {\n    if (dateEscapePressedRef.current) {\n      dateEscapePressedRef.current = false;\n      return;\n    }\n    const parsed = parseDateInput(dateValue, event.start);\n    if (!parsed) {\n      setDateValue(dateOnFocusRef.current);\n      return;\n    }\n    const dayDiff = differenceInCalendarDays(parsed, event.start);\n    if (dayDiff === 0) {\n      setDateValue(formatDateDisplay(event.start));\n      return;\n    }\n    const newStart = addDays(event.start, dayDiff);\n    const newEnd = addDays(event.end, dayDiff);\n    setDateValue(formatDateDisplay(newStart));\n    onEventChange?.({ ...event, start: newStart, end: newEnd });\n  }, [dateValue, event, onEventChange]);\n\n  const handleDateKeyDown = React.useCallback(\n    (e: React.KeyboardEvent<HTMLInputElement>) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        dateRef.current?.blur();\n        return;\n      }\n      if (e.key === \"Escape\") {\n        e.preventDefault();\n        e.stopPropagation();\n        dateEscapePressedRef.current = true;\n        setDateValue(dateOnFocusRef.current);\n        dateRef.current?.blur();\n      }\n    },\n    [],\n  );\n\n  // --- End date input state & handlers (shown only for all-day events) ---\n  const [endDateValue, setEndDateValue] = React.useState(() =>\n    formatDateDisplay(event.end),\n  );\n  const endDateRef = React.useRef<HTMLInputElement>(null);\n  const endDateEscapePressedRef = React.useRef(false);\n  const endDateOnFocusRef = React.useRef(formatDateDisplay(event.end));\n\n  React.useEffect(() => {\n    setEndDateValue(formatDateDisplay(event.end));\n  }, [event.end]);\n\n  const handleEndDateChange = React.useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      setEndDateValue(e.target.value);\n    },\n    [],\n  );\n\n  const handleEndDateFocus = React.useCallback(() => {\n    endDateOnFocusRef.current = formatDateDisplay(event.end);\n    requestAnimationFrame(() => {\n      endDateRef.current?.select();\n    });\n  }, [event.end]);\n\n  const commitEndDate = React.useCallback(() => {\n    if (endDateEscapePressedRef.current) {\n      endDateEscapePressedRef.current = false;\n      return;\n    }\n    const parsed = parseDateInput(endDateValue, event.end);\n    if (!parsed) {\n      setEndDateValue(endDateOnFocusRef.current);\n      return;\n    }\n    const dayDiff = differenceInCalendarDays(parsed, event.end);\n    if (dayDiff === 0) {\n      setEndDateValue(formatDateDisplay(event.end));\n      return;\n    }\n    const newEnd = addDays(event.end, dayDiff);\n    if (newEnd.getTime() < event.start.getTime()) {\n      setEndDateValue(endDateOnFocusRef.current);\n      return;\n    }\n    setEndDateValue(formatDateDisplay(newEnd));\n    onEventChange?.({ ...event, end: newEnd });\n  }, [endDateValue, event, onEventChange]);\n\n  const handleEndDateKeyDown = React.useCallback(\n    (e: React.KeyboardEvent<HTMLInputElement>) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        endDateRef.current?.blur();\n        return;\n      }\n      if (e.key === \"Escape\") {\n        e.preventDefault();\n        e.stopPropagation();\n        endDateEscapePressedRef.current = true;\n        setEndDateValue(endDateOnFocusRef.current);\n        endDateRef.current?.blur();\n      }\n    },\n    [],\n  );\n\n  // --- All-day toggle handler ---\n  /**\n   * Stores the original hours/minutes before toggling to all-day.\n   * When toggling off, these are applied to the current (possibly resized) dates.\n   */\n  const savedTimeOfDayRef = React.useRef<{\n    startHours: number;\n    startMinutes: number;\n    endHours: number;\n    endMinutes: number;\n  } | null>(null);\n\n  const handleAllDayToggle = React.useCallback(\n    (checked: boolean) => {\n      if (checked) {\n        savedTimeOfDayRef.current = {\n          startHours: event.start.getHours(),\n          startMinutes: event.start.getMinutes(),\n          endHours: event.end.getHours(),\n          endMinutes: event.end.getMinutes(),\n        };\n        onEventChange?.({ ...event, isAllDay: true });\n        return;\n      }\n\n      if (savedTimeOfDayRef.current) {\n        const { startHours, startMinutes, endHours, endMinutes } =\n          savedTimeOfDayRef.current;\n        onEventChange?.({\n          ...event,\n          isAllDay: false,\n          start: applyTimeToDate(event.start, startHours, startMinutes),\n          end: applyTimeToDate(event.end, endHours, endMinutes),\n        });\n        savedTimeOfDayRef.current = null;\n        return;\n      }\n\n      /** Default 9 AM – 10 AM when no saved times (e.g., existing all-day event). */\n      const DEFAULT_START_HOUR = 9;\n      const DEFAULT_END_HOUR = 10;\n      onEventChange?.({\n        ...event,\n        isAllDay: false,\n        start: applyTimeToDate(event.start, DEFAULT_START_HOUR, 0),\n        end: applyTimeToDate(event.end, DEFAULT_END_HOUR, 0),\n      });\n    },\n    [event, onEventChange],\n  );\n\n  const otherTypes = EVENT_TYPES.filter((t) => t !== eventType);\n\n  return (\n    <div className=\"flex flex-col gap-3 py-3\">\n      {/* Header */}\n      <div className=\"flex items-center justify-between px-4\">\n        <DropdownMenu\n          open={eventDropdownOpen}\n          onOpenChange={(open) => {\n            setEventDropdownOpen(open);\n            if (open) setHoveredOther(false);\n          }}\n        >\n          <DropdownMenuTrigger asChild>\n            <button\n              type=\"button\"\n              className={cn(\n                \"flex items-center gap-0.5 text-xs font-medium rounded-sm border border-transparent px-2.5 py-1.5 -ml-2.5 gap-1.5 hover:border-[#373737]\",\n                eventDropdownOpen\n                  ? \"bg-[#252525] text-white\"\n                  : \"text-foreground\",\n              )}\n            >\n              {eventType}\n              <ChevronDown\n                className={cn(\n                  \"size-3.5\",\n                  eventDropdownOpen\n                    ? \"text-[#595959]\"\n                    : \"text-[#C7C5C1] dark:text-[#595959]\",\n                )}\n              />\n            </button>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"start\"\n            side=\"left\"\n            sideOffset={12}\n            alignOffset={-4}\n            className=\"min-w-[180px] bg-[#252525] border-[#303030]\"\n            onMouseLeave={() => setHoveredOther(false)}\n          >\n            <DropdownMenuItem\n              className={cn(\n                \"group/item text-xs text-white focus:bg-[#303030] focus:text-white\",\n                !hoveredOther && \"bg-[#303030]\",\n              )}\n              onSelect={() => setEventType(eventType)}\n              onMouseEnter={() => setHoveredOther(false)}\n            >\n              <Check className=\"size-3.5\" />\n              <span className=\"flex-1\">{eventType}</span>\n              <EventTypeHelpIcon tooltip={EVENT_TYPE_TOOLTIPS[eventType]} />\n            </DropdownMenuItem>\n            <DropdownMenuSeparator className=\"bg-[#303030]\" />\n            {otherTypes.map((type) => (\n              <DropdownMenuItem\n                key={type}\n                className=\"group/item text-xs text-white focus:bg-[#303030] focus:text-white pl-8\"\n                onSelect={() => setEventType(type)}\n                onMouseEnter={() => setHoveredOther(true)}\n              >\n                <span className=\"flex-1\">{type}</span>\n                <EventTypeHelpIcon tooltip={EVENT_TYPE_TOOLTIPS[type]} />\n              </DropdownMenuItem>\n            ))}\n          </DropdownMenuContent>\n        </DropdownMenu>\n        <div className=\"flex items-center gap-0.5\">\n          <DropdownMenu>\n            <DropdownMenuTrigger asChild>\n              <Button\n                variant=\"ghost\"\n                size=\"icon\"\n                className=\"size-7 border border-transparent hover:border-[#242424] hover:bg-[#242424] text-[#C7C5C1] dark:text-[#595959]\"\n              >\n                <MoreHorizontal className=\"size-4\" />\n              </Button>\n            </DropdownMenuTrigger>\n            <DropdownMenuContent\n              align=\"start\"\n              side=\"left\"\n              className=\"min-w-[180px] bg-[#252525] border-[#303030]\"\n            >\n              <DropdownMenuItem className=\"text-xs text-white focus:!bg-[#303030] focus:!text-white\">\n                <SquareDashed className=\"size-3.5\" />\n                Cut\n                <DropdownMenuShortcut className=\"text-white/40\">\n                  ⌘X\n                </DropdownMenuShortcut>\n              </DropdownMenuItem>\n              <DropdownMenuItem className=\"text-xs text-white focus:!bg-[#303030] focus:!text-white\">\n                <TabletSmartphone className=\"size-3.5\" />\n                Copy\n                <DropdownMenuShortcut className=\"text-white/40\">\n                  ⌘C\n                </DropdownMenuShortcut>\n              </DropdownMenuItem>\n              <DropdownMenuItem className=\"text-xs text-white focus:!bg-[#303030] focus:!text-white\">\n                <Copy className=\"size-3.5\" />\n                Duplicate\n                <DropdownMenuShortcut className=\"text-white/40\">\n                  ⌘D\n                </DropdownMenuShortcut>\n              </DropdownMenuItem>\n              <DropdownMenuSeparator className=\"bg-[#303030]\" />\n              <DropdownMenuItem className=\"text-xs text-[#E56458] focus:!bg-[#DE5551] focus:!text-white focus:[&>svg]:!text-white focus:[&>[data-slot=dropdown-menu-shortcut]]:!text-white\">\n                <Trash2 className=\"size-3.5 text-[#E56458]\" />\n                Delete\n                <DropdownMenuShortcut className=\"text-white/40 tracking-normal\">\n                  delete\n                </DropdownMenuShortcut>\n              </DropdownMenuItem>\n            </DropdownMenuContent>\n          </DropdownMenu>\n          {headerActions}\n        </div>\n      </div>\n\n      {/* Title */}\n      <input\n        ref={titleRef}\n        type=\"text\"\n        value={titleValue}\n        onChange={handleTitleChange}\n        onFocus={handleTitleFocus}\n        onBlur={commitTitle}\n        onKeyDown={handleTitleKeyDown}\n        placeholder=\"Title\"\n        className=\"text-foreground placeholder:text-[#C7C5C1] dark:placeholder:text-[#595959] mx-2 rounded-sm border border-transparent bg-transparent px-2 py-1.5 text-xs outline-none hover:border-[#373737] focus:border-[#242424] focus:bg-[#242424]\"\n      />\n\n      {/* Divider */}\n      <div className=\"border-border border-t\" />\n\n      {/* Time — muted and non-interactive for all-day events */}\n      {(event.start.getHours() !== 0 ||\n        event.start.getMinutes() !== 0 ||\n        event.end.getHours() !== 0 ||\n        event.end.getMinutes() !== 0) && (\n        <div className=\"flex min-w-0 items-center gap-1 px-2 text-xs\">\n          {/* Start time group — Clock icon + input in one bordered container */}\n          <div\n            className={cn(\n              \"flex shrink-0 items-center gap-2 rounded-sm border border-transparent px-2 py-1.5\",\n              event.isAllDay\n                ? \"cursor-default\"\n                : \"cursor-text hover:border-[#373737] has-[:focus]:border-[#242424] has-[:focus]:bg-[#242424]\",\n            )}\n            onClick={\n              event.isAllDay ? undefined : () => startTimeRef.current?.focus()\n            }\n          >\n            <Clock className=\"size-4 shrink-0 text-[#C7C5C1] dark:text-[#595959]\" />\n            <input\n              ref={startTimeRef}\n              type=\"text\"\n              value={startTimeValue}\n              onChange={handleStartTimeChange}\n              onFocus={handleStartTimeFocus}\n              onBlur={commitStartTime}\n              onKeyDown={handleStartTimeKeyDown}\n              readOnly={event.isAllDay}\n              tabIndex={event.isAllDay ? -1 : undefined}\n              className={cn(\n                \"w-[8ch] font-medium text-xs bg-transparent outline-none border-none p-0\",\n                event.isAllDay\n                  ? \"text-[#C7C5C1] dark:text-[#595959] pointer-events-none\"\n                  : \"text-foreground\",\n              )}\n            />\n          </div>\n          {/* End time group — arrow + input + duration in one bordered container */}\n          <div\n            className={cn(\n              \"flex min-w-0 flex-1 items-center rounded-sm border border-transparent px-2 py-1.5\",\n              event.isAllDay\n                ? \"cursor-default\"\n                : \"cursor-text hover:border-[#373737] has-[:focus]:border-[#242424] has-[:focus]:bg-[#242424]\",\n            )}\n            onClick={\n              event.isAllDay ? undefined : () => endTimeRef.current?.focus()\n            }\n          >\n            <span className=\"mr-2 shrink-0 text-base leading-4 text-[#C7C5C1] dark:text-[#595959]\">\n              →\n            </span>\n            <input\n              ref={endTimeRef}\n              type=\"text\"\n              value={endTimeValue}\n              onChange={handleEndTimeChange}\n              onFocus={handleEndTimeFocus}\n              onBlur={commitEndTime}\n              onKeyDown={handleEndTimeKeyDown}\n              readOnly={event.isAllDay}\n              tabIndex={event.isAllDay ? -1 : undefined}\n              className={cn(\n                \"min-w-0 font-medium text-xs bg-transparent outline-none border-none p-0\",\n                event.isAllDay\n                  ? \"text-[#C7C5C1] dark:text-[#595959] pointer-events-none\"\n                  : \"text-foreground\",\n              )}\n              size={endTimeValue.length}\n            />\n            <span className=\"shrink-0 text-[#C7C5C1] dark:text-[#595959]\">\n              {formatDuration(event.start, event.end)}\n            </span>\n          </div>\n        </div>\n      )}\n\n      {/* Date — editable inline input(s), indented to align with time text */}\n      <div\n        className={cn(\n          \"flex items-center gap-2 -mt-2\",\n          event.start.getHours() !== 0 ||\n            event.start.getMinutes() !== 0 ||\n            event.end.getHours() !== 0 ||\n            event.end.getMinutes() !== 0\n            ? \"ml-8\"\n            : \"ml-4\",\n        )}\n      >\n        {/* Start date */}\n        <div\n          className=\"mr-0 flex min-w-[6.5rem] self-start cursor-text items-center rounded-sm border border-transparent px-2 py-1.5 hover:border-[#373737] has-[:focus]:border-[#242424] has-[:focus]:bg-[#242424]\"\n          onClick={() => dateRef.current?.focus()}\n        >\n          <input\n            ref={dateRef}\n            type=\"text\"\n            value={dateValue}\n            onChange={handleDateChange}\n            onFocus={handleDateFocus}\n            onBlur={commitDate}\n            onKeyDown={handleDateKeyDown}\n            className=\"text-foreground text-xs bg-transparent outline-none border-none p-0\"\n            size={dateValue.length}\n          />\n        </div>\n        {/* End date — only visible for all-day events */}\n        {event.isAllDay && (\n          <div\n            className=\"flex min-w-[6.5rem] self-start cursor-text items-center rounded-sm border border-transparent px-2 py-1.5 hover:border-[#373737] has-[:focus]:border-[#242424] has-[:focus]:bg-[#242424]\"\n            onClick={() => endDateRef.current?.focus()}\n          >\n            <input\n              ref={endDateRef}\n              type=\"text\"\n              value={endDateValue}\n              onChange={handleEndDateChange}\n              onFocus={handleEndDateFocus}\n              onBlur={commitEndDate}\n              onKeyDown={handleEndDateKeyDown}\n              className=\"text-foreground text-xs bg-transparent outline-none border-none p-0\"\n              size={endDateValue.length}\n            />\n          </div>\n        )}\n      </div>\n\n      {optionsExpanded || event.isAllDay ? (\n        <>\n          {/* All-day toggle row — clicking label or row triggers toggle */}\n          <div\n            className=\"flex cursor-default items-center gap-3 px-4\"\n            onClick={() => handleAllDayToggle(!(event.isAllDay ?? false))}\n          >\n            <Switch\n              checked={event.isAllDay ?? false}\n              onCheckedChange={handleAllDayToggle}\n              onClick={(e) => e.stopPropagation()}\n              className=\"data-[state=unchecked]:!bg-[#C7C5C1] dark:data-[state=unchecked]:!bg-[#595959] data-[state=checked]:!bg-[#3A85D3]\"\n            />\n            <span className=\"text-foreground text-xs\">All-day</span>\n          </div>\n\n          {/* Timezone row — hidden when all-day */}\n          {!event.isAllDay && (\n            <div className=\"flex items-center gap-3 px-4\">\n              <Globe className=\"size-4 shrink-0 text-[#C7C5C1] dark:text-[#595959]\" />\n              <TimezoneDisplay timezone={event.timezone ?? \"GMT-3 Sao Paulo\"} />\n            </div>\n          )}\n\n          {/* Recurrence row — active display for recurring, placeholder for non-recurring */}\n          {event.recurrence ? (\n            <div className=\"flex items-center gap-3 px-4\">\n              <RefreshCcw className=\"size-4 shrink-0 text-[#C7C5C1] dark:text-[#595959]\" />\n              <div className=\"flex flex-1 items-center justify-between\">\n                <RecurrenceDisplay recurrence={event.recurrence} />\n                <div className=\"flex items-center\">\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    className=\"size-6 text-[#C7C5C1] dark:text-[#595959]\"\n                    onClick={onPrevWeek}\n                  >\n                    <ChevronLeft className=\"size-3.5\" />\n                  </Button>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    className=\"size-6 text-[#C7C5C1] dark:text-[#595959]\"\n                    onClick={onNextWeek}\n                  >\n                    <ChevronRight className=\"size-3.5\" />\n                  </Button>\n                </div>\n              </div>\n            </div>\n          ) : (\n            <div className=\"flex items-center gap-3 px-4\">\n              <RefreshCcw className=\"size-4 shrink-0 text-[#C7C5C1] dark:text-[#595959]\" />\n              <span className=\"text-xs text-[#C7C5C1] dark:text-[#595959]\">\n                Repeat\n              </span>\n            </div>\n          )}\n        </>\n      ) : (\n        <div className=\"-mt-2 flex items-center pl-8\">\n          <div\n            className=\"group/options flex cursor-default items-center gap-4 rounded-sm px-2 py-1.5 hover:bg-[#E8E8E4] dark:hover:bg-[#242424]\"\n            onClick={() => setOptionsExpanded(true)}\n          >\n            <span className=\"text-xs text-[#C7C5C1] dark:text-[#595959] dark:group-hover/options:text-[#636363]\">\n              All-day\n            </span>\n            <span className=\"text-xs text-[#C7C5C1] dark:text-[#595959] dark:group-hover/options:text-[#636363]\">\n              Time zone\n            </span>\n            <span className=\"text-xs text-[#C7C5C1] dark:text-[#595959] dark:group-hover/options:text-[#636363]\">\n              Repeat\n            </span>\n          </div>\n        </div>\n      )}\n\n      {/* Divider */}\n      <div className=\"border-border border-t\" />\n\n      {/* Field sections */}\n      <div className=\"flex flex-col px-4\">\n        <FieldRow icon={User} label=\"Participants and Rooms\" />\n        <FieldRow icon={Video} label=\"Conferencing\" />\n        <FieldRow icon={NotepadText} label=\"AI Meeting Notes and Docs\" />\n        <FieldRow icon={MapPin} label=\"Location\" value={event.location} />\n      </div>\n\n      {/* Divider */}\n      <div className=\"border-border border-t\" />\n\n      {/* Description */}\n      <div className=\"flex flex-col gap-1 px-4\">\n        <span className=\"text-xs text-[#C7C5C1] dark:text-[#595959]\">\n          Description\n        </span>\n        {event.description && (\n          <span className=\"text-foreground text-xs\">{event.description}</span>\n        )}\n      </div>\n\n      {/* Divider */}\n      <div className=\"border-border border-t\" />\n\n      {/* Calendar */}\n      <div className=\"flex items-center gap-2 px-4\">\n        <div className={cn(\"size-3 rounded-xs\", colorDotClass[color])} />\n        <span className=\"text-foreground text-xs\">\n          {event.calendarEmail ?? event.calendarId ?? \"Calendar\"}\n        </span>\n      </div>\n\n      {/* Status */}\n      <div className=\"mt-1 grid grid-cols-2 pl-9\">\n        <span className=\"text-foreground text-xs font-medium capitalize\">\n          {event.status ?? \"Busy\"}\n        </span>\n        <span className=\"text-foreground text-xs font-medium\">\n          {formatVisibility(event.visibility)}\n        </span>\n      </div>\n\n      {/* Reminders */}\n      <div className=\"mt-1 flex flex-col gap-3 px-4\">\n        <div className=\"flex items-center gap-2\">\n          <Bell className=\"size-4 text-[#C7C5C1] dark:text-[#595959]\" />\n          <span className=\"text-xs text-[#C7C5C1] dark:text-[#595959]\">\n            Reminders\n          </span>\n        </div>\n        {event.reminders &&\n          event.reminders.length > 0 &&\n          event.reminders.map((reminder) => (\n            <span\n              key={`${reminder.amount}-${reminder.unit}`}\n              className=\"text-foreground pl-6 text-xs\"\n            >\n              <span className=\"font-medium\">\n                {reminder.amount}\n                {reminder.unit.replace(/s$/, \"\")}\n              </span>{\" \"}\n              before\n            </span>\n          ))}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/event-detail-panel.tsx"
    },
    {
      "path": "components/layouts/calendar/event-detail-popover.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { PanelRightIcon, X } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { PopoverContent } from \"@/components/ui/popover\";\nimport { EventDetailPanel } from \"./event-detail-panel\";\nimport { useCalendarPopoverBoundary } from \"./calendar-popover-context\";\nimport type { CalendarEvent } from \"./week-view-types\";\n\ninterface EventDetailPopoverProps {\n  event: CalendarEvent;\n  onEventChange?: (event: CalendarEvent) => void;\n  onClose: () => void;\n  onDockToSidebar: () => void;\n  onPrevWeek?: () => void;\n  onNextWeek?: () => void;\n  /** Which side to prefer for the popover. Defaults to \"right\". */\n  side?: \"right\" | \"bottom\" | \"left\" | \"top\";\n  /** Alignment along the side axis. Defaults to \"center\". */\n  align?: \"start\" | \"center\" | \"end\";\n  /**\n   * Override the top collision padding. When omitted the header height is\n   * used so the popover never overlaps the weekday header. All-day events\n   * live *inside* the header, so they pass a small value to avoid being\n   * pushed off-screen.\n   */\n  collisionPaddingTop?: number;\n}\n\nexport function EventDetailPopover({\n  event,\n  onEventChange,\n  onClose,\n  onDockToSidebar,\n  onPrevWeek,\n  onNextWeek,\n  side = \"right\",\n  align = \"center\",\n  collisionPaddingTop,\n}: EventDetailPopoverProps) {\n  const { boundary, headerHeight, view } = useCalendarPopoverBoundary();\n\n  /**\n   * In day view the event trigger spans the full grid width, leaving no room\n   * for a 320px popover on either side within the calendar container.\n   * Skip the collision boundary so Radix uses the viewport instead.\n   */\n  const isDayView = view === \"day\";\n  const effectiveBoundary = isDayView\n    ? undefined\n    : boundary\n      ? [boundary]\n      : undefined;\n\n  const popoverHeaderActions = (\n    <>\n      <Button\n        variant=\"ghost\"\n        size=\"icon\"\n        className=\"size-7 text-[#C7C5C1] dark:text-[#595959]\"\n        onClick={(e) => {\n          e.stopPropagation();\n          onDockToSidebar();\n        }}\n        title=\"Dock to sidebar\"\n      >\n        <PanelRightIcon className=\"size-4\" />\n      </Button>\n      <Button\n        variant=\"ghost\"\n        size=\"icon\"\n        className=\"size-7 text-[#C7C5C1] dark:text-[#595959]\"\n        onClick={(e) => {\n          e.stopPropagation();\n          onClose();\n        }}\n        title=\"Close\"\n      >\n        <X className=\"size-4\" />\n      </Button>\n    </>\n  );\n\n  return (\n    <PopoverContent\n      side={side}\n      align={align}\n      sideOffset={8}\n      collisionPadding={{\n        top: collisionPaddingTop ?? headerHeight,\n        bottom: 8,\n        left: 16,\n        right: 16,\n      }}\n      collisionBoundary={effectiveBoundary}\n      className=\"w-[320px] max-h-[80vh] overflow-y-auto p-0 bg-popover/60 backdrop-blur-xl border shadow-lg rounded-lg\"\n      onOpenAutoFocus={(e) => e.preventDefault()}\n      onCloseAutoFocus={(e) => e.preventDefault()}\n      onInteractOutside={(e) => {\n        const target = e.target as HTMLElement;\n        if (target.closest(\"[data-radix-popper-content-wrapper]\")) {\n          e.preventDefault();\n        }\n      }}\n    >\n      <EventDetailPanel\n        event={event}\n        onEventChange={onEventChange}\n        onPrevWeek={onPrevWeek}\n        onNextWeek={onNextWeek}\n        headerActions={popoverHeaderActions}\n      />\n    </PopoverContent>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/event-detail-popover.tsx"
    },
    {
      "path": "components/layouts/calendar/nav-favorites.tsx",
      "content": "\"use client\";\n\nimport {\n  ArrowUpRight,\n  Link,\n  MoreHorizontal,\n  StarOff,\n  Trash2,\n} from \"lucide-react\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  SidebarGroup,\n  SidebarGroupLabel,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"@/components/ui/sidebar\";\n\nexport function NavFavorites({\n  favorites,\n}: {\n  favorites: {\n    name: string;\n    url: string;\n    emoji: string;\n  }[];\n}) {\n  const { isMobile } = useSidebar();\n\n  return (\n    <SidebarGroup className=\"group-data-[collapsible=icon]:hidden\">\n      <SidebarGroupLabel>Favorites</SidebarGroupLabel>\n      <SidebarMenu>\n        {favorites.map((item) => (\n          <SidebarMenuItem key={item.name}>\n            <SidebarMenuButton asChild>\n              <a href={item.url} title={item.name}>\n                <span>{item.emoji}</span>\n                <span>{item.name}</span>\n              </a>\n            </SidebarMenuButton>\n            <DropdownMenu>\n              <DropdownMenuTrigger asChild>\n                <SidebarMenuAction showOnHover>\n                  <MoreHorizontal />\n                  <span className=\"sr-only\">More</span>\n                </SidebarMenuAction>\n              </DropdownMenuTrigger>\n              <DropdownMenuContent\n                className=\"w-56 rounded-lg\"\n                side={isMobile ? \"bottom\" : \"right\"}\n                align={isMobile ? \"end\" : \"start\"}\n              >\n                <DropdownMenuItem>\n                  <StarOff className=\"text-muted-foreground\" />\n                  <span>Remove from Favorites</span>\n                </DropdownMenuItem>\n                <DropdownMenuSeparator />\n                <DropdownMenuItem>\n                  <Link className=\"text-muted-foreground\" />\n                  <span>Copy Link</span>\n                </DropdownMenuItem>\n                <DropdownMenuItem>\n                  <ArrowUpRight className=\"text-muted-foreground\" />\n                  <span>Open in New Tab</span>\n                </DropdownMenuItem>\n                <DropdownMenuSeparator />\n                <DropdownMenuItem>\n                  <Trash2 className=\"text-muted-foreground\" />\n                  <span>Delete</span>\n                </DropdownMenuItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </SidebarMenuItem>\n        ))}\n        <SidebarMenuItem>\n          <SidebarMenuButton className=\"text-sidebar-foreground/70\">\n            <MoreHorizontal />\n            <span>More</span>\n          </SidebarMenuButton>\n        </SidebarMenuItem>\n      </SidebarMenu>\n    </SidebarGroup>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/nav-favorites.tsx"
    },
    {
      "path": "components/layouts/calendar/nav-main.tsx",
      "content": "\"use client\";\n\nimport { type LucideIcon } from \"lucide-react\";\n\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"@/components/ui/sidebar\";\n\nexport function NavMain({\n  items,\n}: {\n  items: {\n    title: string;\n    url: string;\n    icon: LucideIcon;\n    isActive?: boolean;\n  }[];\n}) {\n  return (\n    <SidebarMenu>\n      {items.map((item) => (\n        <SidebarMenuItem key={item.title}>\n          <SidebarMenuButton asChild isActive={item.isActive}>\n            <a href={item.url}>\n              <item.icon />\n              <span>{item.title}</span>\n            </a>\n          </SidebarMenuButton>\n        </SidebarMenuItem>\n      ))}\n    </SidebarMenu>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/nav-main.tsx"
    },
    {
      "path": "components/layouts/calendar/nav-secondary.tsx",
      "content": "import React from \"react\";\nimport { type LucideIcon } from \"lucide-react\";\n\nimport {\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarMenu,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"@/components/ui/sidebar\";\n\nexport function NavSecondary({\n  items,\n  ...props\n}: {\n  items: {\n    title: string;\n    url: string;\n    icon: LucideIcon;\n    badge?: React.ReactNode;\n  }[];\n} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {\n  return (\n    <SidebarGroup {...props}>\n      <SidebarGroupContent>\n        <SidebarMenu>\n          {items.map((item) => (\n            <SidebarMenuItem key={item.title}>\n              <SidebarMenuButton asChild>\n                <a href={item.url}>\n                  <item.icon />\n                  <span>{item.title}</span>\n                </a>\n              </SidebarMenuButton>\n              {item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}\n            </SidebarMenuItem>\n          ))}\n        </SidebarMenu>\n      </SidebarGroupContent>\n    </SidebarGroup>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/nav-secondary.tsx"
    },
    {
      "path": "components/layouts/calendar/nav-user.tsx",
      "content": "\"use client\";\n\nimport {\n  BadgeCheck,\n  Bell,\n  ChevronsUpDown,\n  CreditCard,\n  LogOut,\n  Sparkles,\n} from \"lucide-react\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"@/components/ui/sidebar\";\n\nexport function NavUser({\n  user,\n}: {\n  user: {\n    name: string;\n    email: string;\n    avatar: string;\n  };\n}) {\n  const { isMobile } = useSidebar();\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <DropdownMenu>\n          <DropdownMenuTrigger asChild>\n            <SidebarMenuButton\n              size=\"lg\"\n              className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n            >\n              <Avatar className=\"h-8 w-8 rounded-lg\">\n                <AvatarImage src={user.avatar} alt={user.name} />\n                <AvatarFallback className=\"rounded-lg\">VN</AvatarFallback>\n              </Avatar>\n              <div className=\"grid flex-1 text-left text-sm leading-tight\">\n                <span className=\"truncate font-medium\">{user.name}</span>\n                <span className=\"truncate text-xs\">{user.email}</span>\n              </div>\n              <ChevronsUpDown className=\"ml-auto size-4\" />\n            </SidebarMenuButton>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            className=\"w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg\"\n            side={isMobile ? \"bottom\" : \"right\"}\n            align=\"start\"\n            sideOffset={4}\n          >\n            <DropdownMenuLabel className=\"p-0 font-normal\">\n              <div className=\"flex items-center gap-2 px-1 py-1.5 text-left text-sm\">\n                <Avatar className=\"h-8 w-8 rounded-lg\">\n                  <AvatarImage src={user.avatar} alt={user.name} />\n                  <AvatarFallback className=\"rounded-lg\">VN</AvatarFallback>\n                </Avatar>\n                <div className=\"grid flex-1 text-left text-sm leading-tight\">\n                  <span className=\"truncate font-medium\">{user.name}</span>\n                  <span className=\"truncate text-xs\">{user.email}</span>\n                </div>\n              </div>\n            </DropdownMenuLabel>\n            <DropdownMenuSeparator />\n            <DropdownMenuGroup>\n              <DropdownMenuItem>\n                <Sparkles />\n                Upgrade to Pro\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n            <DropdownMenuSeparator />\n            <DropdownMenuGroup>\n              <DropdownMenuItem>\n                <BadgeCheck />\n                Account\n              </DropdownMenuItem>\n              <DropdownMenuItem>\n                <CreditCard />\n                Billing\n              </DropdownMenuItem>\n              <DropdownMenuItem>\n                <Bell />\n                Notifications\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem>\n              <LogOut />\n              Log out\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/nav-user.tsx"
    },
    {
      "path": "components/layouts/calendar/nav-workspaces.tsx",
      "content": "import { ChevronRight, MoreHorizontal, Plus } from \"lucide-react\";\n\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport {\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n} from \"@/components/ui/sidebar\";\n\nexport function NavWorkspaces({\n  workspaces,\n}: {\n  workspaces: {\n    name: string;\n    emoji: React.ReactNode;\n    pages: {\n      name: string;\n      emoji: React.ReactNode;\n    }[];\n  }[];\n}) {\n  return (\n    <SidebarGroup>\n      <SidebarGroupLabel>Workspaces</SidebarGroupLabel>\n      <SidebarGroupContent>\n        <SidebarMenu>\n          {workspaces.map((workspace) => (\n            <Collapsible key={workspace.name}>\n              <SidebarMenuItem>\n                <SidebarMenuButton asChild>\n                  <a href=\"#\">\n                    <span>{workspace.emoji}</span>\n                    <span>{workspace.name}</span>\n                  </a>\n                </SidebarMenuButton>\n                <CollapsibleTrigger asChild>\n                  <SidebarMenuAction\n                    className=\"bg-sidebar-accent text-sidebar-accent-foreground left-2 data-[state=open]:rotate-90\"\n                    showOnHover\n                  >\n                    <ChevronRight />\n                  </SidebarMenuAction>\n                </CollapsibleTrigger>\n                <SidebarMenuAction showOnHover>\n                  <Plus />\n                </SidebarMenuAction>\n                <CollapsibleContent>\n                  <SidebarMenuSub>\n                    {workspace.pages.map((page) => (\n                      <SidebarMenuSubItem key={page.name}>\n                        <SidebarMenuSubButton asChild>\n                          <a href=\"#\">\n                            <span>{page.emoji}</span>\n                            <span>{page.name}</span>\n                          </a>\n                        </SidebarMenuSubButton>\n                      </SidebarMenuSubItem>\n                    ))}\n                  </SidebarMenuSub>\n                </CollapsibleContent>\n              </SidebarMenuItem>\n            </Collapsible>\n          ))}\n          <SidebarMenuItem>\n            <SidebarMenuButton className=\"text-sidebar-foreground/70\">\n              <MoreHorizontal />\n              <span>More</span>\n            </SidebarMenuButton>\n          </SidebarMenuItem>\n        </SidebarMenu>\n      </SidebarGroupContent>\n    </SidebarGroup>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/nav-workspaces.tsx"
    },
    {
      "path": "components/layouts/calendar/sidebar-left.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ArrowLeft, CalendarSearch, PanelRightIcon, X } from \"lucide-react\";\nimport {\n  differenceInMinutes,\n  format,\n  isBefore,\n  isSameYear,\n  isToday,\n  isTomorrow,\n  startOfDay,\n} from \"date-fns\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Kbd } from \"@/components/ui/kbd\";\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  useSidebar,\n} from \"@/components/ui/sidebar\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { eventColorStyles } from \"./calendar-event-item\";\nimport { EventDetailPanel } from \"./event-detail-panel\";\nimport type { CalendarEvent } from \"./week-view-types\";\n\ninterface SidebarLeftProps extends React.ComponentProps<typeof Sidebar> {\n  events?: CalendarEvent[];\n  selectedEvent?: CalendarEvent | null;\n  onEventChange?: (event: CalendarEvent) => void;\n  onPrevWeek?: () => void;\n  onNextWeek?: () => void;\n}\n\ninterface DateGroup {\n  key: string;\n  label: string;\n  isToday: boolean;\n  events: CalendarEvent[];\n}\n\nfunction formatDuration(start: Date, end: Date): string {\n  const totalMinutes = differenceInMinutes(end, start);\n  const hours = Math.floor(totalMinutes / 60);\n  const minutes = totalMinutes % 60;\n\n  if (hours === 0) {\n    return `${minutes}min`;\n  }\n  if (minutes === 0) {\n    return `${hours}h`;\n  }\n  return `${hours}h ${minutes}min`;\n}\n\nfunction formatTimeRange(event: CalendarEvent): string {\n  if (event.isAllDay) {\n    return \"All day\";\n  }\n  const startPeriod = format(event.start, \"a\");\n  const endPeriod = format(event.end, \"a\");\n  const endStr = format(event.end, \"h:mm a\").replace(\":00 \", \" \");\n\n  if (startPeriod === endPeriod) {\n    const startStr = format(event.start, \"h:mm\").replace(\":00\", \"\");\n    return `${startStr}\\u2013${endStr}`;\n  }\n\n  const startStr = format(event.start, \"h:mm a\").replace(\":00 \", \" \");\n  return `${startStr}\\u2013${endStr}`;\n}\n\nfunction formatDateHeader(date: Date): {\n  label: string;\n  isTodayGroup: boolean;\n} {\n  if (isToday(date)) {\n    return { label: \"Today\", isTodayGroup: true };\n  }\n  if (isTomorrow(date)) {\n    return { label: \"Tomorrow\", isTodayGroup: false };\n  }\n  if (isSameYear(date, new Date())) {\n    return { label: format(date, \"EEE MMM d\"), isTodayGroup: false };\n  }\n  return { label: format(date, \"EEE MMM d, yyyy\"), isTodayGroup: false };\n}\n\nfunction groupEventsByDate(events: CalendarEvent[]): DateGroup[] {\n  const grouped = new Map<string, CalendarEvent[]>();\n\n  for (const event of events) {\n    const dayKey = format(startOfDay(event.start), \"yyyy-MM-dd\");\n    const existing = grouped.get(dayKey);\n    if (existing) {\n      existing.push(event);\n    } else {\n      grouped.set(dayKey, [event]);\n    }\n  }\n\n  const groups: DateGroup[] = [];\n  for (const [key, groupEvents] of grouped) {\n    const date = groupEvents[0].start;\n    const { label, isTodayGroup } = formatDateHeader(date);\n    const sorted = groupEvents.sort(\n      (a, b) => a.start.getTime() - b.start.getTime(),\n    );\n    groups.push({ key, label, isToday: isTodayGroup, events: sorted });\n  }\n\n  return groups.sort((a, b) => a.key.localeCompare(b.key));\n}\n\nexport function SidebarLeft({\n  events = [],\n  selectedEvent,\n  onEventChange,\n  onPrevWeek,\n  onNextWeek,\n  ...props\n}: SidebarLeftProps) {\n  const { toggleSidebar } = useSidebar();\n  const [searchQuery, setSearchQuery] = React.useState(\"\");\n  const [debouncedQuery, setDebouncedQuery] = React.useState(\"\");\n  const [searchSelectedEvent, setSearchSelectedEvent] =\n    React.useState<CalendarEvent | null>(null);\n  const inputRef = React.useRef<HTMLInputElement>(null);\n\n  const isSearching = searchQuery.trim().length > 0;\n  const isLoadingResults = isSearching && searchQuery !== debouncedQuery;\n\n  React.useEffect(() => {\n    if (!isSearching) {\n      setDebouncedQuery(\"\");\n      return;\n    }\n    const timer = setTimeout(() => {\n      setDebouncedQuery(searchQuery);\n    }, 400);\n    return () => clearTimeout(timer);\n  }, [searchQuery, isSearching]);\n\n  const resolvedSearchEvent = React.useMemo(() => {\n    if (!searchSelectedEvent) return null;\n    return (\n      events.find((e) => e.id === searchSelectedEvent.id) ?? searchSelectedEvent\n    );\n  }, [events, searchSelectedEvent]);\n\n  React.useEffect(() => {\n    if (selectedEvent) {\n      setSearchSelectedEvent(null);\n    }\n  }, [selectedEvent]);\n\n  const searchResults = React.useMemo(() => {\n    if (!debouncedQuery.trim()) {\n      return [];\n    }\n    const query = debouncedQuery.trim().toLowerCase();\n    return events.filter((event) => event.title.toLowerCase().includes(query));\n  }, [events, debouncedQuery]);\n\n  const { pastGroups, todayGroup, futureGroups } = React.useMemo(() => {\n    const allGroups = groupEventsByDate(searchResults);\n    const now = new Date();\n    const todayStart = startOfDay(now);\n\n    const past: DateGroup[] = [];\n    let today: DateGroup | null = null;\n    const future: DateGroup[] = [];\n\n    for (const group of allGroups) {\n      if (group.isToday) {\n        today = group;\n        continue;\n      }\n      const groupDate = new Date(group.key);\n      if (isBefore(groupDate, todayStart)) {\n        past.push(group);\n        continue;\n      }\n      future.push(group);\n    }\n\n    return { pastGroups: past, todayGroup: today, futureGroups: future };\n  }, [searchResults]);\n\n  const hasUpcomingResults = todayGroup !== null || futureGroups.length > 0;\n\n  return (\n    <Sidebar\n      side=\"right\"\n      className=\"border-l !bg-context-panel [&_[data-slot=sidebar-inner]]:!bg-context-panel\"\n      style={{ \"--muted-foreground\": \"#C7C5C1\" } as React.CSSProperties}\n      {...props}\n    >\n      <SidebarHeader className=\"h-14 justify-center px-4\">\n        <div className=\"flex items-center gap-2\">\n          {selectedEvent ? (\n            <div className=\"flex-1\" />\n          ) : resolvedSearchEvent ? (\n            <>\n              <Button\n                variant=\"ghost\"\n                className=\"-ml-2 h-7 gap-2 px-2 has-[>svg]:px-2 text-xs text-[#91908F] justify-start\"\n                onClick={() => setSearchSelectedEvent(null)}\n              >\n                <ArrowLeft className=\"size-4\" />\n                Search\n              </Button>\n              <div className=\"flex-1\" />\n            </>\n          ) : (\n            <div\n              className=\"group/search flex flex-1 cursor-text items-center gap-2 rounded-sm border border-transparent px-1 hover:border-[#F5F5F5] hover:bg-[#F5F5F5] dark:hover:border-[#373737] dark:hover:bg-transparent has-[:focus]:border-[#F5F5F5] has-[:focus]:bg-[#F5F5F5] dark:has-[:focus]:border-[#242424] dark:has-[:focus]:bg-[#242424]\"\n              onClick={() => inputRef.current?.focus()}\n            >\n              <CalendarSearch className=\"size-4 shrink-0 text-[#C7C5C1] dark:text-[#595959]\" />\n              <input\n                ref={inputRef}\n                type=\"text\"\n                placeholder=\"Search events\"\n                value={searchQuery}\n                onChange={(e) => setSearchQuery(e.target.value)}\n                className=\"text-foreground placeholder:text-muted-foreground h-7 w-full border-none bg-transparent p-0 text-xs outline-none\"\n              />\n              {isSearching && (\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"size-7 shrink-0 opacity-0 transition-opacity group-hover/search:opacity-100 group-has-[:focus]/search:opacity-100\"\n                  onClick={(e) => {\n                    e.stopPropagation();\n                    setSearchQuery(\"\");\n                    inputRef.current?.focus();\n                  }}\n                >\n                  <X className=\"size-4\" />\n                </Button>\n              )}\n            </div>\n          )}\n          <Tooltip>\n            <TooltipTrigger asChild>\n              <Button\n                variant=\"ghost\"\n                size=\"icon\"\n                className=\"size-7 shrink-0\"\n                onClick={toggleSidebar}\n              >\n                <PanelRightIcon className=\"size-4\" />\n              </Button>\n            </TooltipTrigger>\n            <TooltipContent side=\"bottom\">\n              Close context panel <Kbd className=\"ml-1\">/</Kbd>\n            </TooltipContent>\n          </Tooltip>\n        </div>\n      </SidebarHeader>\n      <SidebarContent>\n        {selectedEvent ? (\n          <EventDetailPanel\n            event={selectedEvent}\n            onEventChange={onEventChange}\n            onPrevWeek={onPrevWeek}\n            onNextWeek={onNextWeek}\n          />\n        ) : resolvedSearchEvent ? (\n          <EventDetailPanel\n            event={resolvedSearchEvent}\n            onEventChange={onEventChange}\n            onPrevWeek={onPrevWeek}\n            onNextWeek={onNextWeek}\n          />\n        ) : isLoadingResults ? (\n          <div className=\"px-4 pl-8 pt-8\">\n            <p className=\"text-[#ABABA9] dark:text-[#7C7C7C] text-xs\">\n              Searching\n              <AnimatedDots />\n            </p>\n          </div>\n        ) : isSearching ? (\n          <div className=\"flex flex-col pb-16\">\n            {pastGroups.map((group) => (\n              <DateGroupSection\n                key={group.key}\n                group={group}\n                isPast\n                onEventClick={setSearchSelectedEvent}\n              />\n            ))}\n            <div className=\"px-4 pt-5\">\n              <p className=\"-ml-1 text-[#E8533E] text-xs font-semibold pb-3\">\n                Today\n              </p>\n              {todayGroup && (\n                <div className=\"flex flex-col\">\n                  {todayGroup.events.map((event) => (\n                    <SearchResultItem\n                      key={event.id}\n                      event={event}\n                      onClick={setSearchSelectedEvent}\n                    />\n                  ))}\n                </div>\n              )}\n              {!hasUpcomingResults && (\n                <p className=\"text-[#ABABA9] dark:text-[#7F7F7F] pl-4 pt-1 text-xs\">\n                  No upcoming results\n                </p>\n              )}\n            </div>\n            {futureGroups.map((group) => (\n              <DateGroupSection\n                key={group.key}\n                group={group}\n                onEventClick={setSearchSelectedEvent}\n              />\n            ))}\n          </div>\n        ) : (\n          <SidebarGroup className=\"px-3 pt-6\">\n            <SidebarGroupLabel className=\"text-foreground text-xs font-semibold px-0\">\n              Useful shortcuts\n            </SidebarGroupLabel>\n            <SidebarGroupContent>\n              <div className=\"flex flex-col\">\n                <ShortcutRow label=\"Command menu\">\n                  <Kbd>⌘</Kbd>\n                  <Kbd>K</Kbd>\n                </ShortcutRow>\n                <ShortcutRow label=\"Menu bar calendar\">\n                  <Kbd>⌃</Kbd>\n                  <Kbd>⌘</Kbd>\n                  <Kbd>K</Kbd>\n                </ShortcutRow>\n                <ShortcutRow label=\"Toggle sidebar\">\n                  <Kbd>`</Kbd>\n                </ShortcutRow>\n                <ShortcutRow label=\"Show teammate calendar\">\n                  <Kbd>P</Kbd>\n                </ShortcutRow>\n                <ShortcutRow label=\"Go to date\">\n                  <Kbd>.</Kbd>\n                </ShortcutRow>\n                <ShortcutRow label=\"All keyboard shortcuts\">\n                  <Kbd>?</Kbd>\n                </ShortcutRow>\n              </div>\n            </SidebarGroupContent>\n          </SidebarGroup>\n        )}\n      </SidebarContent>\n    </Sidebar>\n  );\n}\n\nfunction DateGroupSection({\n  group,\n  isPast = false,\n  onEventClick,\n}: {\n  group: DateGroup;\n  isPast?: boolean;\n  onEventClick?: (event: CalendarEvent) => void;\n}) {\n  return (\n    <div className=\"px-4 pt-5\">\n      <p\n        className={`-ml-1 text-xs font-semibold pb-3 ${group.isToday ? \"text-[#E8533E]\" : \"text-foreground\"}`}\n      >\n        {group.label}\n      </p>\n      <div className=\"flex flex-col\">\n        {group.events.map((event) => (\n          <SearchResultItem\n            key={event.id}\n            event={event}\n            isPast={isPast}\n            onClick={onEventClick}\n          />\n        ))}\n      </div>\n    </div>\n  );\n}\n\n/** Color tokens for search result items by temporal state */\nconst SEARCH_RESULT_COLORS = {\n  future: {\n    title: \"text-[#32302C] dark:text-[#D4D4D4]\",\n    time: \"text-[#787774] dark:text-[#7F7F7F]\",\n    duration: \"text-[#ABABA9] dark:text-[#5A5A5A]\",\n    hover: \"hover:bg-[#F5F5F5] dark:hover:bg-[#252525]\",\n  },\n  past: {\n    title: \"text-[#989795] dark:text-[#777]\",\n    time: \"text-[#BBBBB9] dark:text-[#4C4C4C]\",\n    duration: \"text-[#D5D5D4] dark:text-[#3A3A3A]\",\n    hover: \"hover:bg-[#FAFAFA] dark:hover:bg-[#1F1F1F]\",\n  },\n} as const;\n\n/**\n * Sequential dot fill animation.\n * Steps: fill dot 0 → fill dot 1 → fill dot 2 → unfill dot 0 → unfill dot 1 → unfill dot 2\n * Each dot is either 10% or 100% opacity based on the current step.\n */\nconst DOT_STEP_INTERVAL_MS = 300;\nconst DOT_STEPS = [\n  [false, false, false],\n  [true, false, false],\n  [true, true, false],\n  [true, true, true],\n  [false, true, true],\n  [false, false, true],\n] as const;\n\nfunction AnimatedDots() {\n  const [step, setStep] = React.useState(0);\n\n  React.useEffect(() => {\n    const interval = setInterval(() => {\n      setStep((prev) => (prev + 1) % DOT_STEPS.length);\n    }, DOT_STEP_INTERVAL_MS);\n    return () => clearInterval(interval);\n  }, []);\n\n  const filled = DOT_STEPS[step];\n\n  return (\n    <span className=\"ml-1 inline-flex items-center gap-0.5\">\n      {[0, 1, 2].map((i) => (\n        <span\n          key={i}\n          className=\"inline-block size-0.5 rounded-full bg-[#ABABA9] dark:bg-[#7C7C7C] transition-opacity duration-200\"\n          style={{ opacity: filled[i] ? 1 : 0.1 }}\n        />\n      ))}\n    </span>\n  );\n}\n\nfunction SearchResultItem({\n  event,\n  isPast = false,\n  onClick,\n}: {\n  event: CalendarEvent;\n  isPast?: boolean;\n  onClick?: (event: CalendarEvent) => void;\n}) {\n  const timeRange = formatTimeRange(event);\n  const duration = event.isAllDay ? \"\" : formatDuration(event.start, event.end);\n  const colors = isPast\n    ? SEARCH_RESULT_COLORS.past\n    : SEARCH_RESULT_COLORS.future;\n\n  return (\n    <div\n      className={`-mx-1 flex cursor-default items-start gap-2.5 rounded-sm px-1 py-2 ${colors.hover}`}\n      onClick={() => onClick?.(event)}\n    >\n      <div\n        className={`-mt-1 -mb-1 w-1 shrink-0 self-stretch rounded-full ${eventColorStyles[event.color ?? \"blue\"].border} ${isPast ? \"opacity-40\" : \"\"}`}\n      />\n      <div className=\"flex min-w-0 flex-col gap-0.5\">\n        <p\n          className={`truncate text-sm font-medium leading-snug ${colors.title}`}\n        >\n          {event.title}\n        </p>\n        <p className=\"text-sm\">\n          <span className={colors.time}>{timeRange}</span>\n          {duration && <span className={colors.duration}> {duration}</span>}\n        </p>\n      </div>\n    </div>\n  );\n}\n\nfunction ShortcutRow({\n  label,\n  children,\n}: {\n  label: string;\n  children: React.ReactNode;\n}) {\n  return (\n    <div className=\"text-muted-foreground flex items-center justify-between py-1 text-xs\">\n      <span>{label}</span>\n      <div className=\"flex items-center gap-1\">{children}</div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/sidebar-left.tsx"
    },
    {
      "path": "components/layouts/calendar/sidebar-right.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Eye, Github, Link2, Plus, UserRound } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Calendars } from \"@/components/layouts/calendar/calendars\";\nimport { DatePicker } from \"@/components/layouts/calendar/date-picker\";\nimport { ModeToggle } from \"@/components/mode-toggle\";\nimport { Button } from \"@/components/ui/button\";\nimport { Kbd } from \"@/components/ui/kbd\";\nimport {\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarSeparator,\n} from \"@/components/ui/sidebar\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nconst SIDEBAR_WIDTH = \"15rem\";\n\nexport type CalendarColor =\n  | \"red\"\n  | \"orange\"\n  | \"yellow\"\n  | \"green\"\n  | \"blue\"\n  | \"purple\"\n  | \"gray\";\n\nexport interface CalendarItem {\n  name: string;\n  color: CalendarColor;\n  visible: boolean;\n  isSubscribed?: boolean;\n}\n\nexport interface CalendarAccount {\n  email: string;\n  calendars: CalendarItem[];\n}\n\n// Sample data grouped by email accounts\nconst data: { accounts: CalendarAccount[] } = {\n  accounts: [\n    {\n      email: \"you@example.com\",\n      calendars: [\n        { name: \"you@example.com\", color: \"red\", visible: true },\n        { name: \"Personal\", color: \"purple\", visible: true },\n        { name: \"Work\", color: \"blue\", visible: true },\n        { name: \"Family\", color: \"orange\", visible: true },\n        { name: \"Side Projects\", color: \"yellow\", visible: true },\n        { name: \"Fitness\", color: \"green\", visible: true },\n        {\n          name: \"Holidays in Brazil\",\n          color: \"green\",\n          visible: true,\n          isSubscribed: true,\n        },\n      ],\n    },\n  ],\n};\n\ninterface SidebarRightProps {\n  open?: boolean;\n  onDateSelect?: (date: Date) => void;\n  currentDate?: Date;\n  visibleDays?: Date[];\n}\n\nexport function SidebarRight({\n  open = true,\n  onDateSelect,\n  currentDate,\n  visibleDays,\n}: SidebarRightProps) {\n  return (\n    <div\n      data-state={open ? \"expanded\" : \"collapsed\"}\n      className=\"text-sidebar-foreground group peer hidden md:block\"\n      style={{ \"--sidebar-width\": SIDEBAR_WIDTH } as React.CSSProperties}\n    >\n      {/* Gap element that handles the space transition */}\n      <div\n        className={cn(\n          \"relative bg-transparent\",\n          open ? \"w-(--sidebar-width)\" : \"w-0\",\n        )}\n      />\n      {/* Sidebar container */}\n      <div\n        className={cn(\n          \"bg-sidebar fixed inset-y-0 left-0 z-10 hidden h-svh w-(--sidebar-width) flex-col overflow-hidden border-r md:flex\",\n          open ? \"left-0\" : \"left-[calc(var(--sidebar-width)*-1)]\",\n        )}\n      >\n        <SidebarContent\n          // Kill horizontal overflow entirely (no row in this sidebar should\n          // need x-scroll; if something grows wider than the column it's a\n          // bug and we want it clipped, not scrollable). Keep vertical\n          // overflow functional but hide the scrollbar visually — clean\n          // look, sticky mini-calendar at top still reachable any time.\n          className=\"overflow-x-hidden [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden\"\n        >\n          <DatePicker\n            onDateSelect={onDateSelect}\n            currentDate={currentDate}\n            visibleDays={visibleDays}\n          />\n          {/* Scheduling Section */}\n          <SidebarGroup className=\"py-0\">\n            <SidebarGroupContent>\n              <SidebarMenu>\n                <SidebarMenuItem>\n                  <SidebarMenuButton className=\"text-sidebar-foreground\">\n                    <Link2 className=\"size-4 text-sidebar-muted-foreground\" />\n                    <span className=\"flex-1 text-sm\">Scheduling</span>\n                    <Eye className=\"size-4 text-sidebar-muted-foreground\" />\n                  </SidebarMenuButton>\n                </SidebarMenuItem>\n              </SidebarMenu>\n            </SidebarGroupContent>\n          </SidebarGroup>\n          {/* Meet with input */}\n          <SidebarGroup className=\"py-2 px-2\">\n            <div className=\"flex items-center gap-2 rounded-sm bg-[#EFEFEE] dark:bg-sidebar px-2 py-1.5\">\n              <UserRound className=\"size-4 shrink-0 text-sidebar-muted-foreground\" />\n              <input\n                type=\"text\"\n                placeholder=\"Meet with...\"\n                className=\"flex-1 bg-transparent text-sm text-sidebar-foreground placeholder:text-sidebar-muted-foreground outline-none\"\n              />\n            </div>\n          </SidebarGroup>\n          <Calendars accounts={data.accounts} />\n          <SidebarSeparator className=\"mx-0\" />\n          <SidebarGroup className=\"py-0\">\n            <SidebarGroupContent>\n              <SidebarMenu>\n                <SidebarMenuItem>\n                  <SidebarMenuButton className=\"text-sidebar-foreground text-sm\">\n                    <Plus className=\"size-4\" />\n                    <span>Add calendar account</span>\n                  </SidebarMenuButton>\n                </SidebarMenuItem>\n              </SidebarMenu>\n            </SidebarGroupContent>\n          </SidebarGroup>\n        </SidebarContent>\n        <SidebarFooter className=\"border-t border-sidebar-border\">\n          <div className=\"flex items-center justify-start gap-1 px-2 py-2\">\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"size-7 text-sidebar-foreground\"\n                  asChild\n                >\n                  <a\n                    href=\"https://ruixen.com\"\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                  >\n                    <Github className=\"size-4\" />\n                  </a>\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"top\">Go to repo</TooltipContent>\n            </Tooltip>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <ModeToggle />\n              </TooltipTrigger>\n              <TooltipContent side=\"top\">\n                Toggle theme <Kbd className=\"ml-1\">⌘</Kbd> <Kbd>⇧</Kbd>{\" \"}\n                <Kbd>L</Kbd>\n              </TooltipContent>\n            </Tooltip>\n          </div>\n        </SidebarFooter>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/sidebar-right.tsx"
    },
    {
      "path": "components/layouts/calendar/team-switcher.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronDown, Plus } from \"lucide-react\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"@/components/ui/sidebar\";\n\nexport function TeamSwitcher({\n  teams,\n}: {\n  teams: {\n    name: string;\n    logo: React.ElementType;\n    plan: string;\n  }[];\n}) {\n  const [activeTeam, setActiveTeam] = React.useState(teams[0]);\n\n  if (!activeTeam) {\n    return null;\n  }\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <DropdownMenu>\n          <DropdownMenuTrigger asChild>\n            <SidebarMenuButton className=\"w-fit px-1.5\">\n              <div className=\"bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-5 items-center justify-center rounded-sm\">\n                <activeTeam.logo className=\"size-3\" />\n              </div>\n              <span className=\"truncate font-medium\">{activeTeam.name}</span>\n              <ChevronDown className=\"opacity-50\" />\n            </SidebarMenuButton>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            className=\"w-64 rounded-lg\"\n            align=\"start\"\n            side=\"bottom\"\n            sideOffset={4}\n          >\n            <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n              Teams\n            </DropdownMenuLabel>\n            {teams.map((team, index) => (\n              <DropdownMenuItem\n                key={team.name}\n                onClick={() => setActiveTeam(team)}\n                className=\"gap-2 p-2\"\n              >\n                <div className=\"flex size-6 items-center justify-center rounded-xs border\">\n                  <team.logo className=\"size-4 shrink-0\" />\n                </div>\n                {team.name}\n                <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>\n              </DropdownMenuItem>\n            ))}\n            <DropdownMenuSeparator />\n            <DropdownMenuItem className=\"gap-2 p-2\">\n              <div className=\"bg-background flex size-6 items-center justify-center rounded-sm border\">\n                <Plus className=\"size-4\" />\n              </div>\n              <div className=\"text-muted-foreground font-medium\">Add team</div>\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/team-switcher.tsx"
    },
    {
      "path": "components/layouts/calendar/view-dropdown.tsx",
      "content": "\"use client\";\n\nimport { CheckIcon, ChevronDownIcon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Kbd, KbdGroup } from \"@/components/ui/kbd\";\n\nimport type {\n  ViewSettings,\n  ViewType,\n} from \"@/components/layouts/calendar/week-view-types\";\n\n/** Display labels for each view type */\nconst VIEW_LABELS: Record<ViewType, string> = {\n  day: \"Day\",\n  week: \"Week\",\n  month: \"Month\",\n};\n\n/** View mode options with their shortcut labels */\nconst VIEW_OPTIONS: Array<{\n  value: ViewType;\n  label: string;\n  shortcuts: Array<string>;\n}> = [\n  { value: \"day\", label: \"Day\", shortcuts: [\"1\", \"or\", \"D\"] },\n  { value: \"week\", label: \"Week\", shortcuts: [\"0\", \"or\", \"W\"] },\n  { value: \"month\", label: \"Month\", shortcuts: [\"M\"] },\n];\n\n/** Number-of-days options for the submenu */\nconst DAYS_OPTIONS = [2, 3, 4, 5, 6, 7, 8, 9] as const;\n\ninterface ViewDropdownProps {\n  view: ViewType;\n  numberOfDays: number;\n  viewSettings: ViewSettings;\n  onSwitchView: (view: ViewType) => void;\n  onSetNumberOfDays: (count: number) => void;\n  onToggleWeekends: () => void;\n  onToggleDeclinedEvents: () => void;\n  onToggleWeekNumbers: () => void;\n}\n\nexport function ViewDropdown({\n  view,\n  numberOfDays,\n  viewSettings,\n  onSwitchView,\n  onSetNumberOfDays,\n  onToggleWeekends,\n  onToggleDeclinedEvents,\n  onToggleWeekNumbers,\n}: ViewDropdownProps) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button variant=\"secondary\" size=\"sm\" className=\"gap-1 px-3\">\n          {VIEW_LABELS[view]}\n          <ChevronDownIcon className=\"size-4 text-muted-foreground\" />\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"start\" sideOffset={8} className=\"w-56\">\n        {VIEW_OPTIONS.map((option) => (\n          <DropdownMenuItem\n            key={option.value}\n            onClick={() => onSwitchView(option.value)}\n          >\n            <CheckIcon\n              className={cn(\n                \"size-4 shrink-0\",\n                view !== option.value && \"invisible\",\n              )}\n            />\n            {option.label}\n            <KbdGroup className=\"ml-auto\">\n              {option.shortcuts.map((s) =>\n                s === \"or\" ? (\n                  <span key={s} className=\"text-muted-foreground text-xs\">\n                    or\n                  </span>\n                ) : (\n                  <Kbd key={s} variant=\"ghost\">\n                    {s}\n                  </Kbd>\n                ),\n              )}\n            </KbdGroup>\n          </DropdownMenuItem>\n        ))}\n\n        <DropdownMenuSeparator />\n\n        {/* Number of days submenu */}\n        <DropdownMenuSub>\n          <DropdownMenuSubTrigger className=\"pl-8\">\n            Number of days\n          </DropdownMenuSubTrigger>\n          <DropdownMenuSubContent>\n            {DAYS_OPTIONS.map((n) => (\n              <DropdownMenuItem key={n} onClick={() => onSetNumberOfDays(n)}>\n                <CheckIcon\n                  className={cn(\n                    \"size-4 shrink-0\",\n                    numberOfDays !== n && \"invisible\",\n                  )}\n                />\n                {n} days\n                <Kbd variant=\"ghost\" className=\"ml-auto\">\n                  {n}\n                </Kbd>\n              </DropdownMenuItem>\n            ))}\n            <DropdownMenuSeparator />\n            <DropdownMenuItem disabled>Other...</DropdownMenuItem>\n          </DropdownMenuSubContent>\n        </DropdownMenuSub>\n\n        <DropdownMenuSeparator />\n\n        {/* View settings submenu */}\n        <DropdownMenuSub>\n          <DropdownMenuSubTrigger className=\"pl-8\">\n            View settings\n          </DropdownMenuSubTrigger>\n          <DropdownMenuSubContent className=\"w-56\">\n            <DropdownMenuCheckboxItem\n              checked={viewSettings.showWeekends}\n              onCheckedChange={() => onToggleWeekends()}\n            >\n              Weekends\n              <KbdGroup className=\"ml-auto\">\n                <Kbd variant=\"ghost\">⌘</Kbd>\n                <Kbd variant=\"ghost\">⇧</Kbd>\n                <Kbd variant=\"ghost\">E</Kbd>\n              </KbdGroup>\n            </DropdownMenuCheckboxItem>\n\n            <DropdownMenuCheckboxItem\n              checked={viewSettings.showDeclinedEvents}\n              onCheckedChange={() => onToggleDeclinedEvents()}\n            >\n              Declined eve...\n              <KbdGroup className=\"ml-auto\">\n                <Kbd variant=\"ghost\">⌘</Kbd>\n                <Kbd variant=\"ghost\">⇧</Kbd>\n                <Kbd variant=\"ghost\">D</Kbd>\n              </KbdGroup>\n            </DropdownMenuCheckboxItem>\n\n            <DropdownMenuCheckboxItem\n              checked={viewSettings.showWeekNumbers}\n              onCheckedChange={() => onToggleWeekNumbers()}\n            >\n              Week numbers\n            </DropdownMenuCheckboxItem>\n\n            <DropdownMenuSeparator />\n\n            <DropdownMenuItem disabled>\n              General settings\n              <KbdGroup className=\"ml-auto\">\n                <Kbd variant=\"ghost\">⌘</Kbd>\n                <Kbd variant=\"ghost\">,</Kbd>\n              </KbdGroup>\n            </DropdownMenuItem>\n          </DropdownMenuSubContent>\n        </DropdownMenuSub>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/view-dropdown.tsx"
    },
    {
      "path": "components/layouts/calendar/week-view-all-day-row.tsx",
      "content": "\"use client\";\n\nimport type React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"@/lib/utils\";\nimport { isPast, isSameDay } from \"date-fns\";\nimport { useCallback } from \"react\";\nimport { calculateAllDayEventRows } from \"@/lib/layouts/event-utils\";\nimport { AllDayEventItem } from \"./calendar-event-item\";\nimport { useCalendarPopoverBoundary } from \"./calendar-popover-context\";\nimport type { CalendarEvent, WeekViewAllDayRowProps } from \"./week-view-types\";\n\nconst ALL_DAY_EVENT_HEIGHT = 24;\nconst ALL_DAY_ROW_GAP = 2;\n\n/**\n * All-day row for displaying all-day events\n * Shows \"All-day\" label on the left with day columns\n */\nexport function WeekViewAllDayRow({\n  days,\n  allDayEvents = [],\n  onEventClick,\n  selectedEventId,\n  scrollStyle,\n  allDayResizeState,\n  onAllDayResizeMouseDown,\n  allDayScrollContentRef,\n  onEventChange,\n  onContextMenuOpenChange,\n  isSidebarOpen,\n  onDockToSidebar,\n  onClosePopover,\n  onPrevWeek,\n  onNextWeek,\n  visibleStartIndex,\n  visibleCount,\n  dayColumnWidth,\n  className,\n}: WeekViewAllDayRowProps) {\n  const allEventRows = calculateAllDayEventRows(allDayEvents, days);\n\n  // Filter out events entirely within buffer columns so they don't peek\n  // through due to sub-pixel rendering at the scroll boundary.\n  const visibleEnd =\n    visibleStartIndex != null && visibleCount != null\n      ? visibleStartIndex + visibleCount - 1\n      : days.length - 1;\n  const eventRows =\n    visibleStartIndex != null\n      ? allEventRows.filter(\n          ({ startColumn, endColumn }) =>\n            endColumn >= visibleStartIndex && startColumn <= visibleEnd,\n        )\n      : allEventRows;\n  const maxRow =\n    eventRows.length > 0 ? Math.max(...eventRows.map((r) => r.row)) + 1 : 0;\n  const contentHeight =\n    maxRow > 0 ? maxRow * (ALL_DAY_EVENT_HEIGHT + ALL_DAY_ROW_GAP) + 8 : 32;\n\n  const isMoveDrag =\n    allDayResizeState?.isResizing && allDayResizeState.edge === \"move\";\n\n  return (\n    <div\n      className={cn(\n        \"border-border flex border-t border-b bg-background\",\n        className,\n      )}\n    >\n      {/* All-day label */}\n      <div className=\"border-border text-muted-foreground flex w-16 flex-shrink-0 items-start justify-end border-r px-2 py-2 text-xxs\">\n        All-day\n      </div>\n\n      {/* Day columns for all-day events - wrapped for scroll sync */}\n      <div className=\"flex-1 overflow-hidden\">\n        <div ref={allDayScrollContentRef} style={scrollStyle}>\n          <div className=\"relative\" style={{ minHeight: `${contentHeight}px` }}>\n            {/* Background grid */}\n            <div\n              className=\"absolute inset-0 grid\"\n              style={{ gridTemplateColumns: `repeat(${days.length}, 1fr)` }}\n            >\n              {days.map((day) => {\n                const isWeekend =\n                  day.date.getDay() === 0 || day.date.getDay() === 6;\n                return (\n                  <div\n                    key={day.date.toISOString()}\n                    className={cn(\n                      \"border-border border-l first:border-l-0 h-full\",\n                      isWeekend && \"bg-calendar-weekend\",\n                    )}\n                  />\n                );\n              })}\n            </div>\n\n            {/* Events */}\n            <div className=\"relative py-1 px-0.5\">\n              {eventRows.map(({ event, startColumn, endColumn, row }) => {\n                const isBeingResized =\n                  allDayResizeState?.eventId === event.id &&\n                  allDayResizeState.isResizing;\n                const isBeingMoved =\n                  isBeingResized && allDayResizeState.edge === \"move\";\n\n                // For move: ghost stays at original, event renders at target\n                // For resize: event renders at target (current columns)\n                const displayStartColumn = isBeingResized\n                  ? isBeingMoved\n                    ? startColumn\n                    : allDayResizeState.currentStartColumn\n                  : startColumn;\n                const displayEndColumn = isBeingResized\n                  ? isBeingMoved\n                    ? endColumn\n                    : allDayResizeState.currentEndColumn\n                  : endColumn;\n\n                return (\n                  <AllDayEventRow\n                    key={event.id}\n                    event={event}\n                    startColumn={displayStartColumn}\n                    endColumn={displayEndColumn}\n                    row={row}\n                    totalColumns={days.length}\n                    days={days}\n                    onEventClick={onEventClick}\n                    isSelected={event.id === selectedEventId}\n                    onAllDayResizeMouseDown={onAllDayResizeMouseDown}\n                    originalStartColumn={startColumn}\n                    originalEndColumn={endColumn}\n                    isBeingResized={isBeingResized}\n                    isBeingMoved={isBeingMoved}\n                    onEventChange={onEventChange}\n                    onContextMenuOpenChange={onContextMenuOpenChange}\n                    isSidebarOpen={isSidebarOpen}\n                    onDockToSidebar={onDockToSidebar}\n                    onClosePopover={onClosePopover}\n                    onPrevWeek={onPrevWeek}\n                    onNextWeek={onNextWeek}\n                    visibleStartIndex={visibleStartIndex}\n                  />\n                );\n              })}\n\n              {/* Placeholder at target position during move */}\n              {isMoveDrag &&\n                (() => {\n                  const movedRow = eventRows.find(\n                    (r) => r.event.id === allDayResizeState.eventId,\n                  );\n                  if (!movedRow) return null;\n                  return (\n                    <AllDayPlaceholderRow\n                      event={movedRow.event}\n                      startColumn={allDayResizeState.currentStartColumn}\n                      endColumn={allDayResizeState.currentEndColumn}\n                      row={movedRow.row}\n                      totalColumns={days.length}\n                    />\n                  );\n                })()}\n            </div>\n          </div>\n        </div>\n      </div>\n\n      {/* Floating drag copy via portal */}\n      {isMoveDrag &&\n        allDayResizeState.clientX != null &&\n        allDayResizeState.clientY != null &&\n        (() => {\n          const movedRow = eventRows.find(\n            (r) => r.event.id === allDayResizeState.eventId,\n          );\n          if (!movedRow) return null;\n          const span = movedRow.endColumn - movedRow.startColumn + 1;\n          const colWidthPx = dayColumnWidth ?? 100;\n          const floatingWidth = span * colWidthPx;\n          const offsetX = allDayResizeState.cursorOffsetX ?? 0;\n\n          return createPortal(\n            <div\n              className=\"pointer-events-none\"\n              style={{\n                position: \"fixed\",\n                top: 0,\n                left: 0,\n                width: \"100vw\",\n                height: \"100vh\",\n                zIndex: 9999,\n              }}\n            >\n              <div\n                style={{\n                  position: \"fixed\",\n                  left: `${allDayResizeState.clientX - offsetX}px`,\n                  top: `${allDayResizeState.clientY - (allDayResizeState.cursorOffsetY ?? 12)}px`,\n                  width: `${floatingWidth}px`,\n                }}\n              >\n                <AllDayEventItem\n                  event={movedRow.event}\n                  spanStart\n                  spanEnd\n                  dragVariant=\"dragging\"\n                />\n              </div>\n            </div>,\n            document.body,\n          );\n        })()}\n    </div>\n  );\n}\n\ninterface AllDayEventRowProps {\n  event: CalendarEvent;\n  startColumn: number;\n  endColumn: number;\n  row: number;\n  totalColumns: number;\n  days: WeekViewAllDayRowProps[\"days\"];\n  onEventClick?: (event: CalendarEvent) => void;\n  isSelected?: boolean;\n  onAllDayResizeMouseDown?: WeekViewAllDayRowProps[\"onAllDayResizeMouseDown\"];\n  originalStartColumn: number;\n  originalEndColumn: number;\n  isBeingResized?: boolean;\n  isBeingMoved?: boolean;\n  /** Callback when an event is changed (e.g. color change from context menu) */\n  onEventChange?: (event: CalendarEvent) => void;\n  /** Callback when context menu open state changes */\n  onContextMenuOpenChange?: (open: boolean) => void;\n  isSidebarOpen?: boolean;\n  onDockToSidebar?: () => void;\n  onClosePopover?: () => void;\n  onPrevWeek?: () => void;\n  onNextWeek?: () => void;\n  /** Index of the first visible column (for sticky-title offset) */\n  visibleStartIndex?: number;\n}\n\nfunction AllDayEventRow({\n  event,\n  startColumn,\n  endColumn,\n  row,\n  totalColumns,\n  days,\n  onEventClick,\n  isSelected,\n  onAllDayResizeMouseDown,\n  originalStartColumn,\n  originalEndColumn,\n  isBeingResized,\n  isBeingMoved,\n  onEventChange,\n  onContextMenuOpenChange,\n  isSidebarOpen,\n  onDockToSidebar,\n  onClosePopover,\n  onPrevWeek,\n  onNextWeek,\n  visibleStartIndex,\n}: AllDayEventRowProps) {\n  const { view } = useCalendarPopoverBoundary();\n  const isDayView = view === \"day\";\n\n  const left = (startColumn / totalColumns) * 100;\n  // Day view uses a smaller right gap than week view so events nearly fill\n  // the column but still show a sliver of the grid \\u2014 matching Notion Calendar.\n  const columnWidth = 100 / totalColumns;\n  const rightGap = isDayView ? columnWidth * 0.02 : columnWidth * 0.08;\n  const width = ((endColumn - startColumn + 1) / totalColumns) * 100 - rightGap;\n  const top = row * (ALL_DAY_EVENT_HEIGHT + ALL_DAY_ROW_GAP);\n\n  // During resize, both edges are always visible so force rounding on both sides\n  const spanStart =\n    isBeingResized || isSameDay(event.start, days[startColumn].date);\n  const spanEnd = isBeingResized || isSameDay(event.end, days[endColumn].date);\n\n  /**\n   * In day view with buffer days (3 columns: buffer | visible | buffer),\n   * the visible column is index 1. Multi-day events that start in the left\n   * buffer (column 0) have their title off-screen. Calculate the percentage\n   * offset needed to push the title into the visible area.\n   *\n   * CSS `padding-left` percentages are relative to the **containing block's\n   * width** (the wrapper div), not the grid container. We must convert from\n   * container-relative coordinates to wrapper-relative coordinates:\n   *   offset = (visibleStart% - left%) \\u00D7 (100 / width%)\n   */\n  const visibleColumnIndex = visibleStartIndex ?? 1;\n  const visibleStartPercent = (visibleColumnIndex / totalColumns) * 100;\n  const hiddenContainerPercent = isDayView\n    ? Math.max(0, visibleStartPercent - left)\n    : 0;\n  /** Small extra nudge (in container %) so the title doesn't sit flush\n   *  against the visible column edge \\u2014 gives it a bit of breathing room. */\n  const TITLE_NUDGE_PERCENT = 0.1;\n  const titleOffsetPercent =\n    hiddenContainerPercent > 0 && width > 0\n      ? ((hiddenContainerPercent + TITLE_NUDGE_PERCENT) / width) * 100\n      : 0;\n\n  const handleResizeMouseDown = useCallback(\n    (\n      e: React.MouseEvent,\n      ev: CalendarEvent,\n      edge: \"left\" | \"right\" | \"move\",\n    ) => {\n      onAllDayResizeMouseDown?.(\n        e,\n        ev,\n        edge,\n        originalStartColumn,\n        originalEndColumn,\n      );\n    },\n    [onAllDayResizeMouseDown, originalStartColumn, originalEndColumn],\n  );\n\n  return (\n    <div\n      className=\"absolute\"\n      style={{\n        left: `${left}%`,\n        width: `${width}%`,\n        top: `${top}px`,\n        paddingLeft: \"2px\",\n        paddingRight: \"2px\",\n      }}\n    >\n      <AllDayEventItem\n        event={event}\n        isPast={isPast(event.end)}\n        isSelected={isBeingMoved ? false : isSelected}\n        onClick={onEventClick}\n        spanStart={spanStart}\n        spanEnd={spanEnd}\n        onResizeMouseDown={handleResizeMouseDown}\n        onEventChange={onEventChange}\n        onContextMenuOpenChange={onContextMenuOpenChange}\n        isSidebarOpen={isSidebarOpen}\n        onDockToSidebar={onDockToSidebar}\n        onClosePopover={onClosePopover}\n        onPrevWeek={onPrevWeek}\n        onNextWeek={onNextWeek}\n        titleOffsetPercent={titleOffsetPercent}\n        dragVariant={isBeingMoved ? \"ghost\" : undefined}\n      />\n    </div>\n  );\n}\n\n/** Placeholder border-only outline rendered at the target position during move */\nfunction AllDayPlaceholderRow({\n  event,\n  startColumn,\n  endColumn,\n  row,\n  totalColumns,\n}: {\n  event: CalendarEvent;\n  startColumn: number;\n  endColumn: number;\n  row: number;\n  totalColumns: number;\n}) {\n  const columnWidth = 100 / totalColumns;\n  const left = (startColumn / totalColumns) * 100;\n  const rightGap = columnWidth * 0.08;\n  const width = ((endColumn - startColumn + 1) / totalColumns) * 100 - rightGap;\n  const top = row * (ALL_DAY_EVENT_HEIGHT + ALL_DAY_ROW_GAP);\n\n  return (\n    <div\n      className=\"absolute pointer-events-none\"\n      style={{\n        left: `${left}%`,\n        width: `${width}%`,\n        top: `${top}px`,\n        paddingLeft: \"2px\",\n        paddingRight: \"2px\",\n        zIndex: 25,\n      }}\n    >\n      <AllDayEventItem\n        event={event}\n        spanStart\n        spanEnd\n        dragVariant=\"placeholder\"\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/week-view-all-day-row.tsx"
    },
    {
      "path": "components/layouts/calendar/week-view-day-columns.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { WeekViewDayColumnsProps } from \"./week-view-types\";\n\n/**\n * Gets the browser's timezone abbreviation\n */\nfunction getTimezoneAbbreviation(): string {\n  const date = new Date();\n  const timeZoneString = date.toLocaleTimeString(\"en-US\", {\n    timeZoneName: \"short\",\n  });\n  const match = timeZoneString.match(/\\s([A-Z]{2,5})$/);\n\n  if (match) {\n    return match[1];\n  }\n\n  // Fallback to offset format\n  const offset = -date.getTimezoneOffset();\n  const hours = Math.floor(Math.abs(offset) / 60);\n  const sign = offset >= 0 ? \"+\" : \"-\";\n  return `GMT${sign}${hours}`;\n}\n\n/**\n * Day column headers showing day names and date numbers\n * Includes timezone label on the left (unless standalone mode)\n * Highlights the current day\n */\nexport function WeekViewDayColumns({\n  days,\n  standalone,\n  className,\n}: WeekViewDayColumnsProps) {\n  const timezone = getTimezoneAbbreviation();\n\n  // Standalone mode: just render the day columns (used inside scroll container)\n  if (standalone) {\n    return (\n      <div\n        className={cn(\"grid\", className)}\n        style={{ gridTemplateColumns: `repeat(${days.length}, 1fr)` }}\n      >\n        {days.map((day) => (\n          <div\n            key={day.date.toISOString()}\n            className={cn(\n              \"flex items-center justify-center py-2 text-sm\",\n              day.isToday ? \"gap-0.5 \" : \"gap-0\",\n            )}\n          >\n            <span\n              className={cn(\n                day.isToday\n                  ? \"text-foreground font-medium\"\n                  : \"text-muted-foreground font-normal\",\n              )}\n            >\n              {day.dayName}\n            </span>\n            <span\n              className={cn(\n                \"flex h-5 w-[1.2rem] items-center justify-center rounded-xs text-sm\",\n                day.isToday\n                  ? \"bg-primary text-primary-foreground font-medium\"\n                  : \"text-muted-foreground\",\n              )}\n            >\n              {day.dayNumber}\n            </span>\n          </div>\n        ))}\n      </div>\n    );\n  }\n\n  return (\n    <div\n      className={cn(\"grid bg-background\", className)}\n      style={{ gridTemplateColumns: \"4rem 1fr\" }}\n    >\n      {/* Timezone label */}\n      <div className=\"text-muted-foreground flex items-center justify-end pr-2 text-xxs\">\n        {timezone}\n      </div>\n\n      {/* Day columns */}\n      <div\n        className=\"grid\"\n        style={{ gridTemplateColumns: `repeat(${days.length}, 1fr)` }}\n      >\n        {days.map((day) => (\n          <div\n            key={day.date.toISOString()}\n            className={cn(\n              \"flex items-center justify-center py-2 text-sm\",\n              day.isToday ? \"gap-0.5 \" : \"gap-0\",\n            )}\n          >\n            <span\n              className={cn(\n                day.isToday\n                  ? \"text-foreground font-medium\"\n                  : \"text-muted-foreground font-normal\",\n              )}\n            >\n              {day.dayName}\n            </span>\n            <span\n              className={cn(\n                \"flex h-5 w-[1.2rem] items-center justify-center rounded-xs text-sm\",\n                day.isToday\n                  ? \"bg-primary text-primary-foreground font-medium\"\n                  : \"text-muted-foreground\",\n              )}\n            >\n              {day.dayNumber}\n            </span>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/week-view-day-columns.tsx"
    },
    {
      "path": "components/layouts/calendar/week-view-grid.tsx",
      "content": "\"use client\";\n\nimport React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { isSameDay, startOfDay, addDays } from \"date-fns\";\nimport { cn } from \"@/lib/utils\";\nimport { isPast } from \"date-fns\";\nimport { calculatePositionedEvents } from \"@/lib/layouts/event-utils\";\nimport { CalendarEventItem } from \"./calendar-event-item\";\nimport { useCalendarPopoverBoundary } from \"./calendar-popover-context\";\nimport type {\n  CalendarEvent,\n  EventDragState,\n  EventResizeState,\n  PositionedEvent,\n  WeekViewGridProps,\n} from \"./week-view-types\";\n\n/**\n * Main grid displaying hour/day intersection cells with events\n * Each cell represents one hour in one day\n */\nexport function WeekViewGrid({\n  days,\n  hours,\n  hourHeight,\n  events = [],\n  onEventClick,\n  selectedEventId,\n  dragState,\n  onEventDragMouseDown,\n  resizeState,\n  onEventResizeMouseDown,\n  onEventChange,\n  onContextMenuOpenChange,\n  isSidebarOpen,\n  onDockToSidebar,\n  onClosePopover,\n  onPrevWeek,\n  onNextWeek,\n  className,\n}: WeekViewGridProps) {\n  const { view } = useCalendarPopoverBoundary();\n  const isDayView = view === \"day\";\n  const gridRef = React.useRef<HTMLDivElement>(null);\n  const [gridWidth, setGridWidth] = React.useState(0);\n\n  React.useEffect(() => {\n    const el = gridRef.current;\n    if (!el) return;\n\n    const observer = new ResizeObserver((entries) => {\n      for (const entry of entries) {\n        setGridWidth(entry.contentRect.width);\n      }\n    });\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, []);\n\n  return (\n    <div ref={gridRef} className={cn(\"relative\", className)}>\n      {/* Background grid */}\n      <div\n        className=\"grid\"\n        style={{\n          gridTemplateColumns: `repeat(${days.length}, 1fr)`,\n          gridTemplateRows: `repeat(${hours.length}, ${hourHeight}px)`,\n        }}\n      >\n        {hours.map((hourSlot) =>\n          days.map((day) => {\n            const isWeekend =\n              day.date.getDay() === 0 || day.date.getDay() === 6;\n            return (\n              <div\n                key={`${day.date.toISOString()}-${hourSlot.hour}`}\n                className={cn(\n                  \"border-border border-b border-l\",\n                  isWeekend && \"bg-calendar-weekend\",\n                )}\n              />\n            );\n          }),\n        )}\n      </div>\n\n      {/* Events layer */}\n      <div\n        className=\"absolute inset-0 grid pointer-events-none\"\n        style={{ gridTemplateColumns: `repeat(${days.length}, 1fr)` }}\n      >\n        {days.map((day) => {\n          /**\n           * Day view uses a smaller right gap than week view so events\n           * nearly fill the column but still show a sliver of the grid\n           * line — matching Notion Calendar's day-view styling.\n           */\n          const rightGap = isDayView ? 2 : 8;\n          const positionedEvents = calculatePositionedEvents(\n            events,\n            day,\n            rightGap,\n          );\n\n          return (\n            <DayEventsColumn\n              key={day.date.toISOString()}\n              columnDate={day.date}\n              events={positionedEvents}\n              hourHeight={hourHeight}\n              onEventClick={onEventClick}\n              selectedEventId={selectedEventId}\n              dragState={dragState}\n              onEventDragMouseDown={onEventDragMouseDown}\n              resizeState={resizeState}\n              onEventResizeMouseDown={onEventResizeMouseDown}\n              onEventChange={onEventChange}\n              onContextMenuOpenChange={onContextMenuOpenChange}\n              isSidebarOpen={isSidebarOpen}\n              onDockToSidebar={onDockToSidebar}\n              onClosePopover={onClosePopover}\n              onPrevWeek={onPrevWeek}\n              onNextWeek={onNextWeek}\n            />\n          );\n        })}\n      </div>\n\n      {/* Resize placeholder overlay — rendered at grid level for cross-day support */}\n      {resizeState?.isResizing &&\n        !isSameDay(\n          resizeState.currentStartDate,\n          resizeState.currentEndDate,\n        ) && (\n          <ResizePlaceholderOverlay\n            days={days}\n            hourHeight={hourHeight}\n            resizeState={resizeState}\n          />\n        )}\n\n      {/* Drag placeholder overlay — rendered at grid level for cross-column support */}\n      {dragState?.isDragging && (\n        <DragPlaceholderOverlay\n          days={days}\n          hourHeight={hourHeight}\n          dragState={dragState}\n        />\n      )}\n\n      {/* Floating dragging copy — rendered at grid level so it can move freely */}\n      {dragState?.isDragging && (\n        <FloatingDragCopy\n          days={days}\n          hourHeight={hourHeight}\n          dragState={dragState}\n          gridWidth={gridWidth}\n        />\n      )}\n    </div>\n  );\n}\n\ninterface DragPlaceholderOverlayProps {\n  days: WeekViewGridProps[\"days\"];\n  hourHeight: number;\n  dragState: EventDragState;\n}\n\nfunction DragPlaceholderOverlay({\n  days,\n  hourHeight,\n  dragState,\n}: DragPlaceholderOverlayProps) {\n  // Find the target column index using dragState.currentDate\n  const targetColumnIndex = days.findIndex((d) =>\n    isSameDay(d.date, dragState.currentDate),\n  );\n\n  if (targetColumnIndex === -1) return null;\n\n  // Build a minimal PositionedEvent from the drag state event\n  const placeholderPositioned: PositionedEvent = {\n    event: dragState.event,\n    top: 0,\n    height: 0,\n    left: 0,\n    width: 92,\n    column: 0,\n    totalColumns: 1,\n  };\n\n  return (\n    <div\n      className=\"absolute inset-0 grid pointer-events-none\"\n      style={{ gridTemplateColumns: `repeat(${days.length}, 1fr)` }}\n    >\n      {days.map((day, i) => {\n        if (i !== targetColumnIndex) {\n          return <div key={day.date.toISOString()} />;\n        }\n\n        return (\n          <div key={day.date.toISOString()} className=\"relative\">\n            <CalendarEventItem\n              key={`${dragState.eventId}-placeholder`}\n              positionedEvent={placeholderPositioned}\n              hourHeight={hourHeight}\n              dragVariant=\"placeholder\"\n              overrideStart={dragState.currentStart}\n              overrideEnd={dragState.currentEnd}\n            />\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\ninterface ResizePlaceholderOverlayProps {\n  days: WeekViewGridProps[\"days\"];\n  hourHeight: number;\n  resizeState: EventResizeState;\n}\n\nfunction ResizePlaceholderOverlay({\n  days,\n  hourHeight,\n  resizeState,\n}: ResizePlaceholderOverlayProps) {\n  if (resizeState.effectiveEdge === \"bottom\") {\n    return (\n      <BottomEdgeOverlay\n        days={days}\n        hourHeight={hourHeight}\n        resizeState={resizeState}\n      />\n    );\n  }\n\n  return (\n    <TopEdgeOverlay\n      days={days}\n      hourHeight={hourHeight}\n      resizeState={resizeState}\n    />\n  );\n}\n\nfunction BottomEdgeOverlay({\n  days,\n  hourHeight,\n  resizeState,\n}: ResizePlaceholderOverlayProps) {\n  const endDay = resizeState.currentEndDate;\n\n  const startColIndex = days.findIndex((d) =>\n    isSameDay(d.date, resizeState.currentStartDate),\n  );\n  const endColIndex = days.findIndex((d) => isSameDay(d.date, endDay));\n\n  if (startColIndex === -1 || endColIndex === -1) return null;\n  if (endColIndex <= startColIndex) return null;\n\n  return (\n    <div\n      className=\"absolute inset-0 grid pointer-events-none\"\n      style={{ gridTemplateColumns: `repeat(${days.length}, 1fr)` }}\n    >\n      {days.map((day, i) => {\n        // Skip start column (handled by DayEventsColumn) and columns outside range\n        if (i <= startColIndex || i > endColIndex) {\n          return <div key={day.date.toISOString()} />;\n        }\n\n        const isEndColumn = i === endColIndex;\n        const segmentPosition = isEndColumn\n          ? (\"end\" as const)\n          : (\"middle\" as const);\n\n        const midnight = startOfDay(day.date);\n        const overrideStart = midnight;\n        const overrideEnd = isEndColumn\n          ? resizeState.currentEnd\n          : addDays(midnight, 1);\n\n        const positioned: PositionedEvent = {\n          event: resizeState.event,\n          top: 0,\n          height: 0,\n          left: 0,\n          width: 92,\n          column: 0,\n          totalColumns: 1,\n          segmentPosition,\n        };\n\n        return (\n          <div key={day.date.toISOString()} className=\"relative\">\n            <CalendarEventItem\n              positionedEvent={positioned}\n              hourHeight={hourHeight}\n              isSelected\n              overrideStart={overrideStart}\n              overrideEnd={overrideEnd}\n            />\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\nfunction TopEdgeOverlay({\n  days,\n  hourHeight,\n  resizeState,\n}: ResizePlaceholderOverlayProps) {\n  const startDay = resizeState.currentStartDate;\n\n  const startColIndex = days.findIndex((d) => isSameDay(d.date, startDay));\n  const endColIndex = days.findIndex((d) =>\n    isSameDay(d.date, resizeState.currentEndDate),\n  );\n\n  if (startColIndex === -1 || endColIndex === -1) return null;\n  if (endColIndex <= startColIndex) return null;\n\n  return (\n    <div\n      className=\"absolute inset-0 grid pointer-events-none\"\n      style={{ gridTemplateColumns: `repeat(${days.length}, 1fr)` }}\n    >\n      {days.map((day, i) => {\n        // Skip end column (handled by DayEventsColumn) and columns outside range\n        if (i < startColIndex || i >= endColIndex) {\n          return <div key={day.date.toISOString()} />;\n        }\n\n        const isStartColumn = i === startColIndex;\n        const segmentPosition = isStartColumn\n          ? (\"start\" as const)\n          : (\"middle\" as const);\n\n        const midnight = startOfDay(day.date);\n        const overrideStart = isStartColumn\n          ? resizeState.currentStart\n          : midnight;\n        const overrideEnd = addDays(midnight, 1);\n\n        const positioned: PositionedEvent = {\n          event: resizeState.event,\n          top: 0,\n          height: 0,\n          left: 0,\n          width: 92,\n          column: 0,\n          totalColumns: 1,\n          segmentPosition,\n        };\n\n        return (\n          <div key={day.date.toISOString()} className=\"relative\">\n            <CalendarEventItem\n              positionedEvent={positioned}\n              hourHeight={hourHeight}\n              isSelected\n              overrideStart={overrideStart}\n              overrideEnd={overrideEnd}\n            />\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\ninterface FloatingDragCopyProps {\n  days: WeekViewGridProps[\"days\"];\n  hourHeight: number;\n  dragState: EventDragState;\n  gridWidth: number;\n}\n\nfunction FloatingDragCopy({\n  days,\n  hourHeight,\n  dragState,\n  gridWidth,\n}: FloatingDragCopyProps) {\n  const floatingPositioned: PositionedEvent = {\n    event: dragState.event,\n    top: 0,\n    height: 0,\n    left: 0,\n    width: 92,\n    column: 0,\n    totalColumns: 1,\n  };\n\n  const durationMinutes =\n    (dragState.currentEnd.getTime() - dragState.currentStart.getTime()) / 60000;\n  const heightPx = (durationMinutes / 60) * hourHeight;\n  const columnWidthPx =\n    (days.length > 0 ? gridWidth / days.length : 200) * 0.92;\n\n  return createPortal(\n    <div\n      className=\"pointer-events-none\"\n      style={{\n        position: \"fixed\",\n        top: 0,\n        left: 0,\n        width: \"100vw\",\n        height: \"100vh\",\n        zIndex: 9999,\n      }}\n    >\n      <CalendarEventItem\n        key={`${dragState.eventId}-dragging`}\n        positionedEvent={floatingPositioned}\n        hourHeight={hourHeight}\n        dragVariant=\"dragging\"\n        overrideStart={dragState.currentStart}\n        overrideEnd={dragState.currentEnd}\n        cursorY={dragState.clientY}\n        cursorX={dragState.clientX}\n        fixedWidth={columnWidthPx}\n        fixedHeight={heightPx}\n      />\n    </div>,\n    document.body,\n  );\n}\n\ninterface DayEventsColumnProps {\n  columnDate: Date;\n  events: ReturnType<typeof calculatePositionedEvents>;\n  hourHeight: number;\n  onEventClick?: (event: CalendarEvent) => void;\n  selectedEventId?: string;\n  dragState?: EventDragState;\n  onEventDragMouseDown?: (e: React.MouseEvent, event: CalendarEvent) => void;\n  resizeState?: EventResizeState;\n  onEventResizeMouseDown?: (\n    e: React.MouseEvent,\n    event: CalendarEvent,\n    edge: \"top\" | \"bottom\",\n  ) => void;\n  onEventChange?: (event: CalendarEvent) => void;\n  onContextMenuOpenChange?: (open: boolean) => void;\n  isSidebarOpen?: boolean;\n  onDockToSidebar?: () => void;\n  onClosePopover?: () => void;\n  onPrevWeek?: () => void;\n  onNextWeek?: () => void;\n}\n\nfunction renderColumnGhost(\n  positionedEvent: PositionedEvent,\n  hourHeight: number,\n) {\n  return (\n    <CalendarEventItem\n      key={`${positionedEvent.event.id}-ghost`}\n      positionedEvent={positionedEvent}\n      hourHeight={hourHeight}\n      dragVariant=\"ghost\"\n    />\n  );\n}\n\nfunction DayEventsColumn({\n  columnDate,\n  events,\n  hourHeight,\n  onEventClick,\n  selectedEventId,\n  dragState,\n  onEventDragMouseDown,\n  resizeState,\n  onEventResizeMouseDown,\n  onEventChange,\n  onContextMenuOpenChange,\n  isSidebarOpen,\n  onDockToSidebar,\n  onClosePopover,\n  onPrevWeek,\n  onNextWeek,\n}: DayEventsColumnProps) {\n  return (\n    <div className=\"relative h-full pointer-events-auto\">\n      {events.map((positionedEvent) => {\n        const eventId = positionedEvent.event.id;\n        const isBeingDragged =\n          dragState?.isDragging && dragState.eventId === eventId;\n\n        if (isBeingDragged) {\n          return renderColumnGhost(positionedEvent, hourHeight);\n        }\n\n        const isBeingResized =\n          resizeState?.isResizing && resizeState.eventId === eventId;\n\n        if (isBeingResized) {\n          const { effectiveEdge, currentStartDate, currentEndDate } =\n            resizeState;\n          const isCrossDay = !isSameDay(currentStartDate, currentEndDate);\n\n          // Determine if this column is the anchor column\n          const isAnchorColumn =\n            (effectiveEdge === \"bottom\" &&\n              isSameDay(columnDate, currentStartDate)) ||\n            (effectiveEdge === \"top\" && isSameDay(columnDate, currentEndDate));\n\n          // Check if this column is within the new range at all\n          const colTime = columnDate.getTime();\n          const inRange =\n            colTime >= currentStartDate.getTime() &&\n            colTime <= currentEndDate.getTime();\n\n          // Non-anchor columns with original segments: render as ghost\n          // Columns outside new range with original segments: render as ghost\n          if (!isAnchorColumn || !inRange) {\n            return renderColumnGhost(positionedEvent, hourHeight);\n          }\n\n          // Anchor column rendering\n          let displayStart: Date;\n          let displayEnd: Date;\n          let segmentPosition: \"start\" | \"middle\" | \"end\" | \"full\";\n\n          if (!isCrossDay) {\n            // Same day: show currentStart to currentEnd\n            displayStart = resizeState.currentStart;\n            displayEnd = resizeState.currentEnd;\n            segmentPosition = \"full\";\n          } else if (effectiveEdge === \"bottom\") {\n            // Anchor is start column: show currentStart to end-of-day\n            displayStart = resizeState.currentStart;\n            displayEnd = addDays(startOfDay(columnDate), 1);\n            segmentPosition = \"start\";\n          } else {\n            // Anchor is end column: show start-of-day to currentEnd\n            displayStart = startOfDay(columnDate);\n            displayEnd = resizeState.currentEnd;\n            segmentPosition = \"end\";\n          }\n\n          const resizePositioned = { ...positionedEvent, segmentPosition };\n\n          return (\n            <React.Fragment key={eventId}>\n              {renderColumnGhost(positionedEvent, hourHeight)}\n              <CalendarEventItem\n                key={`${eventId}-resizing`}\n                positionedEvent={resizePositioned}\n                hourHeight={hourHeight}\n                isPast={isPast(positionedEvent.event.end)}\n                isSelected={isCrossDay || eventId === selectedEventId}\n                overrideStart={displayStart}\n                overrideEnd={displayEnd}\n                onEventChange={onEventChange}\n              />\n            </React.Fragment>\n          );\n        }\n\n        return (\n          <CalendarEventItem\n            key={eventId}\n            positionedEvent={positionedEvent}\n            hourHeight={hourHeight}\n            isPast={isPast(positionedEvent.event.end)}\n            isSelected={eventId === selectedEventId}\n            onClick={onEventClick}\n            onDragMouseDown={onEventDragMouseDown}\n            onResizeMouseDown={onEventResizeMouseDown}\n            onEventChange={onEventChange}\n            onContextMenuOpenChange={onContextMenuOpenChange}\n            isSidebarOpen={isSidebarOpen}\n            onDockToSidebar={onDockToSidebar}\n            onClosePopover={onClosePopover}\n            onPrevWeek={onPrevWeek}\n            onNextWeek={onNextWeek}\n          />\n        );\n      })}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/week-view-grid.tsx"
    },
    {
      "path": "components/layouts/calendar/week-view-time-axis.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { WeekViewTimeAxisProps } from \"./week-view-types\";\n\n/**\n * Left sidebar displaying hourly time labels using CSS Grid\n * Uses same grid row structure as main grid for guaranteed alignment\n * Width matches the timezone/all-day label column (4rem)\n */\nexport function WeekViewTimeAxis({\n  hours,\n  hourHeight,\n  className,\n}: WeekViewTimeAxisProps) {\n  return (\n    <div\n      className={cn(\"grid w-16 flex-shrink-0\", className)}\n      style={{\n        gridTemplateRows: `repeat(${hours.length}, ${hourHeight}px)`,\n        gridTemplateColumns: \"1fr\",\n      }}\n    >\n      {hours.map((hourSlot) => (\n        <div\n          key={hourSlot.hour}\n          className=\"text-muted-foreground relative pr-2 text-right text-xxs\"\n        >\n          {/* Show label at top of each cell, skip 12 AM (hour 0) */}\n          {hourSlot.hour > 0 && (\n            <span className=\"absolute top-0 right-2 -translate-y-[55%] leading-none\">\n              {hourSlot.label}\n            </span>\n          )}\n        </div>\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/week-view-time-axis.tsx"
    },
    {
      "path": "components/layouts/calendar/week-view-time-indicator.tsx",
      "content": "\"use client\";\n\nimport { format } from \"date-fns\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { WeekViewTimeIndicatorProps } from \"./week-view-types\";\n\n/**\n * Current time indicator showing time badge and horizontal lines\n * - Time badge with current time (e.g., \"5:48PM\") on the left\n * - Thick line on today's column\n * - Thin line on other day columns\n * Updates position every minute\n */\nexport function WeekViewTimeIndicator({\n  days,\n  hourHeight,\n  scrollDays,\n  scrollStyle,\n  behindSelection,\n  className,\n}: WeekViewTimeIndicatorProps) {\n  const [currentTime, setCurrentTime] = React.useState(() => new Date());\n\n  // Check if today is visible in the current week\n  const todayIndex = days.findIndex((day) => day.isToday);\n  const isTodayVisible = todayIndex !== -1;\n\n  // Update time every minute\n  React.useEffect(() => {\n    const interval = setInterval(() => {\n      setCurrentTime(new Date());\n    }, 60000);\n\n    return () => clearInterval(interval);\n  }, []);\n\n  if (!isTodayVisible) {\n    return null;\n  }\n\n  // Calculate position based on current time\n  const minutesSinceMidnight =\n    currentTime.getHours() * 60 + currentTime.getMinutes();\n  const totalMinutesInDay = 24 * 60;\n  const totalGridHeight = hourHeight * 24;\n  const topPosition =\n    (minutesSinceMidnight / totalMinutesInDay) * totalGridHeight;\n\n  // Format time as \"H:MMAM/PM\" (e.g., \"5:48PM\")\n  const formattedTime = format(currentTime, \"h:mma\").toUpperCase();\n\n  const lineDays = scrollDays ?? days;\n  const lineTodayIndex = lineDays.findIndex((d) => d.isToday);\n\n  const linesContent = (\n    <div className=\"flex items-center\">\n      {lineDays.map((day, index) => (\n        <React.Fragment key={day.date.toISOString()}>\n          {index === lineTodayIndex && (\n            <div className=\"relative flex-shrink-0\">\n              <div className=\"bg-primary h-3 w-[3px] translate-y-[0.5px] rounded-full shadow-[0_0_0_1px_white] dark:shadow-[0_0_0_1px_black]\" />\n              <div className=\"absolute top-[6.5px] left-[2px] bg-primary h-[1px] w-[2px] z-10\" />\n              <div className=\"absolute top-[5.5px] left-[2px] bg-primary h-[1px] w-[2px] z-10\" />\n              <div className=\"absolute top-[4.5px] left-[2px] bg-primary h-[1px] w-[2px] z-10\" />\n            </div>\n          )}\n          <div\n            className={cn(\n              \"flex-1 bg-primary\",\n              day.isToday\n                ? \"h-[3px] rounded-r-full shadow-[0_0_0_1px_white] dark:shadow-[0_0_0_1px_black]\"\n                : \"h-[0.5px]\",\n            )}\n          />\n        </React.Fragment>\n      ))}\n    </div>\n  );\n\n  return (\n    <div\n      className={cn(\n        \"pointer-events-none absolute left-0 right-0\",\n        behindSelection ? \"z-10\" : \"z-20\",\n        className,\n      )}\n      style={{ top: topPosition }}\n    >\n      <div className=\"flex -translate-y-1/2 items-center\">\n        {/* Time badge - positioned in the time axis area */}\n        <div className=\"flex w-16 flex-shrink-0 items-center justify-end pr-1\">\n          <span className=\"bg-primary text-primary-foreground rounded-xs px-1 py-0.5 text-xxs font-medium\">\n            {formattedTime}\n          </span>\n        </div>\n\n        {/* Horizontal lines across day columns */}\n        {scrollStyle ? (\n          <div className=\"flex-1 overflow-hidden\">\n            <div style={scrollStyle}>{linesContent}</div>\n          </div>\n        ) : (\n          linesContent\n        )}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/week-view-time-indicator.tsx"
    },
    {
      "path": "components/layouts/calendar/week-view-types.ts",
      "content": "import type React from \"react\";\n\n/**\n * Calendar view mode \\u2014 \\u201cday\\u201d shows a single column, \\u201cweek\\u201d shows 7 columns\n */\nexport type ViewType = \"day\" | \"week\" | \"month\";\n\n/**\n * View settings for display preferences (toggleable from the view dropdown)\n */\nexport interface ViewSettings {\n  showWeekends: boolean;\n  showDeclinedEvents: boolean;\n  showWeekNumbers: boolean;\n}\n\n/**\n * Represents a single day in the week view\n */\nexport interface WeekDay {\n  /** The full Date object for this day */\n  date: Date;\n  /** Short day name (e.g., \"Sun\", \"Mon\") */\n  dayName: string;\n  /** Day of month (1-31) */\n  dayNumber: number;\n  /** Whether this day is today */\n  isToday: boolean;\n}\n\n/**\n * Represents a single hour slot in the time axis\n */\nexport interface HourSlot {\n  /** Hour in 24-hour format (0-23) */\n  hour: number;\n  /** Formatted label (e.g., \"12 AM\", \"1 PM\") */\n  label: string;\n}\n\n/**\n * Props for the main WeekView component\n */\nexport interface WeekViewProps {\n  /** Calendar view mode. Defaults to \"week\" */\n  view?: ViewType;\n  /** Reference date to show the week for. Defaults to today */\n  currentDate?: Date;\n  /** Day the week starts on. Defaults to 0 (Sunday) */\n  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;\n  /** Events to display on the calendar */\n  events?: CalendarEvent[];\n  /** Optional click handler for events */\n  onEventClick?: (event: CalendarEvent) => void;\n  /** ID of the currently selected event */\n  selectedEventId?: string;\n  /** Callback when clicking empty calendar space (not on an event) */\n  onBackgroundClick?: () => void;\n  /** Callback when the displayed date changes (via scroll navigation) */\n  onDateChange?: (date: Date) => void;\n  /** Callback when the visible days change during scroll (real-time updates) */\n  onVisibleDaysChange?: (days: Date[]) => void;\n  /** Callback when an event is changed (e.g. dragged to a new time) */\n  onEventChange?: (event: CalendarEvent) => void;\n  /** Whether the right sidebar is open */\n  isSidebarOpen?: boolean;\n  /** Callback to dock popover to sidebar (opens sidebar) */\n  onDockToSidebar?: () => void;\n  /** Callback to close popover (deselect event) */\n  onClosePopover?: () => void;\n  /** Navigate to previous week */\n  onPrevWeek?: () => void;\n  /** Navigate to next week */\n  onNextWeek?: () => void;\n  /** Optional className for the root element */\n  className?: string;\n}\n\n/**\n * Props for the WeekViewDayColumns component\n */\nexport interface WeekViewDayColumnsProps {\n  /** Array of days to display */\n  days: WeekDay[];\n  /** When true, renders without the timezone/grid wrapper (used in scroll container) */\n  standalone?: boolean;\n  /** Optional className */\n  className?: string;\n}\n\n/**\n * Props for the WeekViewTimeAxis component\n */\nexport interface WeekViewTimeAxisProps {\n  /** Array of hour slots to display */\n  hours: HourSlot[];\n  /** Height of each hour row in pixels */\n  hourHeight: number;\n  /** Optional className */\n  className?: string;\n}\n\n/**\n * Props for the WeekViewGrid component\n */\nexport interface WeekViewGridProps {\n  /** Array of days for columns */\n  days: WeekDay[];\n  /** Array of hour slots for rows */\n  hours: HourSlot[];\n  /** Height of each hour row in pixels */\n  hourHeight: number;\n  /** Events to display on the grid */\n  events?: CalendarEvent[];\n  /** Optional click handler for events */\n  onEventClick?: (event: CalendarEvent) => void;\n  /** ID of the currently selected event */\n  selectedEventId?: string;\n  /** Current drag state if an event is being dragged */\n  dragState?: EventDragState;\n  /** Mousedown handler to initiate event drag */\n  onEventDragMouseDown?: (e: React.MouseEvent, event: CalendarEvent) => void;\n  /** Current resize state if an event is being resized */\n  resizeState?: EventResizeState;\n  /** Mousedown handler to initiate event resize */\n  onEventResizeMouseDown?: (\n    e: React.MouseEvent,\n    event: CalendarEvent,\n    edge: \"top\" | \"bottom\",\n  ) => void;\n  /** Callback when an event is changed (e.g. color change from context menu) */\n  onEventChange?: (event: CalendarEvent) => void;\n  /** Callback when context menu open state changes */\n  onContextMenuOpenChange?: (open: boolean) => void;\n  /** Whether the right sidebar is open */\n  isSidebarOpen?: boolean;\n  /** Callback to dock popover to sidebar */\n  onDockToSidebar?: () => void;\n  /** Callback to close popover */\n  onClosePopover?: () => void;\n  /** Navigate to previous week */\n  onPrevWeek?: () => void;\n  /** Navigate to next week */\n  onNextWeek?: () => void;\n  /** Optional className */\n  className?: string;\n}\n\n/**\n * Props for the WeekViewTimeIndicator component\n */\nexport interface WeekViewTimeIndicatorProps {\n  /** Array of days in the current week view */\n  days: WeekDay[];\n  /** Height of each hour row in pixels */\n  hourHeight: number;\n  /** Buffered days array for scroll-synchronized line rendering */\n  scrollDays?: WeekDay[];\n  /** Scroll transform style to apply to the lines */\n  scrollStyle?: React.CSSProperties;\n  /** Whether to render behind selected events */\n  behindSelection?: boolean;\n  /** Optional className */\n  className?: string;\n}\n\n/**\n * Props for the WeekViewAllDayRow component\n */\nexport interface WeekViewAllDayRowProps {\n  /** Array of days to display */\n  days: WeekDay[];\n  /** All-day events to display */\n  allDayEvents?: CalendarEvent[];\n  /** Optional click handler for events */\n  onEventClick?: (event: CalendarEvent) => void;\n  /** ID of the currently selected event */\n  selectedEventId?: string;\n  /** Optional scroll transform style for horizontal scroll sync */\n  scrollStyle?: React.CSSProperties;\n  /** Current all-day resize state */\n  allDayResizeState?: AllDayResizeState;\n  /** Mousedown handler to initiate all-day event resize or drag */\n  onAllDayResizeMouseDown?: (\n    e: React.MouseEvent,\n    event: CalendarEvent,\n    edge: \"left\" | \"right\" | \"move\",\n    startColumn: number,\n    endColumn: number,\n  ) => void;\n  /** Callback when an event is changed */\n  onEventChange?: (event: CalendarEvent) => void;\n  /** Callback when context menu open state changes */\n  onContextMenuOpenChange?: (open: boolean) => void;\n  /** Ref to attach to the scroll content div for column measurements */\n  allDayScrollContentRef?: React.RefObject<HTMLDivElement>;\n  /** Whether the right sidebar is open */\n  isSidebarOpen?: boolean;\n  /** Callback to dock popover to sidebar */\n  onDockToSidebar?: () => void;\n  /** Callback to close popover */\n  onClosePopover?: () => void;\n  /** Navigate to previous week */\n  onPrevWeek?: () => void;\n  /** Navigate to next week */\n  onNextWeek?: () => void;\n  /** Index of the first visible column (used to skip buffer-only events in day view) */\n  visibleStartIndex?: number;\n  /** Number of visible columns (defaults to days.length when omitted) */\n  visibleCount?: number;\n  /** Width of a single day column in pixels (for floating drag copy sizing) */\n  dayColumnWidth?: number;\n  /** Optional className */\n  className?: string;\n}\n\n/**\n * Represents an event reminder\n */\nexport interface EventReminder {\n  amount: number;\n  unit: \"minutes\" | \"hours\" | \"days\";\n}\n\n/**\n * Represents a calendar event\n */\nexport interface CalendarEvent {\n  /** Unique identifier for the event */\n  id: string;\n  /** Event title */\n  title: string;\n  /** Start date and time */\n  start: Date;\n  /** End date and time */\n  end: Date;\n  /** Whether this is an all-day event */\n  isAllDay?: boolean;\n  /** Event color (for styling) */\n  color?: EventColor;\n  /** Calendar ID this event belongs to */\n  calendarId?: string;\n  /** Optional description */\n  description?: string;\n  /** Optional location */\n  location?: string;\n  /** Timezone string (e.g. \"GMT-3 Sao Paulo\") */\n  timezone?: string;\n  /** Recurrence rule display string (e.g. \"Every week on Thu\") */\n  recurrence?: string;\n  /** Reminders list */\n  reminders?: EventReminder[];\n  /** Busy/Free status */\n  status?: \"busy\" | \"free\";\n  /** Visibility setting */\n  visibility?: \"default\" | \"public\" | \"private\";\n  /** Calendar account email for display */\n  calendarEmail?: string;\n}\n\n/**\n * Predefined event colors\n */\nexport type EventColor =\n  | \"red\"\n  | \"orange\"\n  | \"yellow\"\n  | \"green\"\n  | \"blue\"\n  | \"purple\"\n  | \"gray\";\n\n/**\n * Represents a positioned event for rendering in the grid\n */\nexport interface PositionedEvent {\n  /** The original event */\n  event: CalendarEvent;\n  /** Top position as percentage from the day start */\n  top: number;\n  /** Height as percentage of the day */\n  height: number;\n  /** Left position as percentage (for overlap handling) */\n  left: number;\n  /** Width as percentage (for overlap handling) */\n  width: number;\n  /** Column index when events overlap */\n  column: number;\n  /** Total columns when events overlap */\n  totalColumns: number;\n  /** Segment position for multi-day timed events (controls corner rounding) */\n  segmentPosition?: \"start\" | \"middle\" | \"end\" | \"full\";\n}\n\n/**\n * Drag variant for rendering events in different visual states during drag\n */\nexport type EventDragVariant = \"default\" | \"ghost\" | \"dragging\" | \"placeholder\";\n\n/**\n * State of an in-progress event drag operation\n */\nexport interface EventDragState {\n  /** ID of the event being dragged */\n  eventId: string;\n  /** The original event being dragged (preserved across week navigations) */\n  event: CalendarEvent;\n  /** Original start time before drag */\n  originalStart: Date;\n  /** Original end time before drag */\n  originalEnd: Date;\n  /** Current snapped start time during drag */\n  currentStart: Date;\n  /** Current snapped end time during drag */\n  currentEnd: Date;\n  /** Target day for the placeholder (decoupled from currentStart for cross-column drag) */\n  currentDate: Date;\n  /** Whether the drag threshold has been met */\n  isDragging: boolean;\n  /** Raw cursor Y position in px (unsnapped, for smooth dragging copy) */\n  cursorY: number;\n  /** Raw cursor X position in px relative to grid container */\n  cursorX: number;\n  /** Viewport clientX for fixed-position dragging copy */\n  clientX: number;\n  /** Viewport clientY for fixed-position dragging copy */\n  clientY: number;\n}\n\n/**\n * State of an in-progress event resize operation\n */\nexport interface EventResizeState {\n  /** ID of the event being resized */\n  eventId: string;\n  /** The original event being resized */\n  event: CalendarEvent;\n  /** Original start time before resize */\n  originalStart: Date;\n  /** Original end time before resize */\n  originalEnd: Date;\n  /** Current snapped start time during resize */\n  currentStart: Date;\n  /** Current snapped end time during resize */\n  currentEnd: Date;\n  /** Which edge was originally grabbed */\n  edge: \"top\" | \"bottom\";\n  /** Which edge the cursor is effectively on (flips when crossing anchor) */\n  effectiveEdge: \"top\" | \"bottom\";\n  /** Whether the drag threshold has been met */\n  isResizing: boolean;\n  /** Target day column for the end during cross-day bottom resize */\n  currentEndDate: Date;\n  /** Target day column for the start during cross-day top resize */\n  currentStartDate: Date;\n}\n\n/**\n * State of an in-progress all-day event resize operation\n */\nexport interface AllDayResizeState {\n  /** ID of the event being resized */\n  eventId: string;\n  /** The original event being resized */\n  event: CalendarEvent;\n  /** Original start column index in the buffered days array */\n  originalStartColumn: number;\n  /** Original end column index in the buffered days array */\n  originalEndColumn: number;\n  /** Current start column index during resize */\n  currentStartColumn: number;\n  /** Current end column index during resize */\n  currentEndColumn: number;\n  /** Which edge is being dragged, or \"move\" for drag-and-drop */\n  edge: \"left\" | \"right\" | \"move\";\n  /** Whether the drag threshold has been met */\n  isResizing: boolean;\n  /** Viewport-relative cursor X during move (for floating copy) */\n  clientX?: number;\n  /** Viewport-relative cursor Y during move (for floating copy) */\n  clientY?: number;\n  /** Offset from cursor to event left edge at mousedown (px) */\n  cursorOffsetX?: number;\n  /** Offset from cursor to event top edge at mousedown (px) */\n  cursorOffsetY?: number;\n}\n\n/**\n * Props for the CalendarEventItem component\n */\nexport interface CalendarEventItemProps {\n  /** The positioned event to render */\n  positionedEvent: PositionedEvent;\n  /** Height of each hour in pixels */\n  hourHeight: number;\n  /** Whether the event is in the past */\n  isPast?: boolean;\n  /** Whether the event is currently selected */\n  isSelected?: boolean;\n  /** Optional click handler */\n  onClick?: (event: CalendarEvent) => void;\n  /** Drag variant for visual state during drag */\n  dragVariant?: EventDragVariant;\n  /** Override start time (for dragging/placeholder positioning) */\n  overrideStart?: Date;\n  /** Override end time (for dragging/placeholder positioning) */\n  overrideEnd?: Date;\n  /** Mousedown handler to initiate drag */\n  onDragMouseDown?: (e: React.MouseEvent, event: CalendarEvent) => void;\n  /** Mousedown handler to initiate resize */\n  onResizeMouseDown?: (\n    e: React.MouseEvent,\n    event: CalendarEvent,\n    edge: \"top\" | \"bottom\",\n  ) => void;\n  /** Callback when an event is changed (e.g. color change from context menu) */\n  onEventChange?: (event: CalendarEvent) => void;\n  /** Raw cursor Y position for smooth dragging copy */\n  cursorY?: number;\n  /** Raw cursor X position for smooth dragging copy */\n  cursorX?: number;\n  /** Fixed width in px (for free-floating dragging copy) */\n  fixedWidth?: number;\n  /** Fixed height in px (for free-floating dragging copy) */\n  fixedHeight?: number;\n  /** Callback when context menu open state changes */\n  onContextMenuOpenChange?: (open: boolean) => void;\n  /** Whether the right sidebar is open (controls popover visibility) */\n  isSidebarOpen?: boolean;\n  /** Callback to dock popover to sidebar */\n  onDockToSidebar?: () => void;\n  /** Callback to close popover (deselect event) */\n  onClosePopover?: () => void;\n  /** Navigate to previous week */\n  onPrevWeek?: () => void;\n  /** Navigate to next week */\n  onNextWeek?: () => void;\n  /** Optional className */\n  className?: string;\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/week-view-types.ts"
    },
    {
      "path": "components/layouts/calendar/week-view.tsx",
      "content": "\"use client\";\n\nimport {\n  addDays,\n  differenceInCalendarDays,\n  eachDayOfInterval,\n  format,\n  getWeek,\n  isToday,\n} from \"date-fns\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { isMultiDayEvent } from \"@/lib/layouts/event-utils\";\nimport { useHorizontalScroll } from \"@/hooks/layouts/use-horizontal-scroll\";\nimport { useEventDrag } from \"@/hooks/layouts/use-event-drag\";\nimport { useEventResize } from \"@/hooks/layouts/use-event-resize\";\nimport { useAllDayResize } from \"@/hooks/layouts/use-all-day-resize\";\nimport type {\n  HourSlot,\n  ViewType,\n  WeekDay,\n  WeekViewProps,\n} from \"./week-view-types\";\nimport { WeekViewAllDayRow } from \"./week-view-all-day-row\";\nimport { WeekViewDayColumns } from \"./week-view-day-columns\";\nimport { WeekViewGrid } from \"./week-view-grid\";\nimport { WeekViewTimeAxis } from \"./week-view-time-axis\";\nimport { WeekViewTimeIndicator } from \"./week-view-time-indicator\";\nimport { CalendarPopoverBoundaryProvider } from \"./calendar-popover-context\";\n\n/** Minimum height of each hour row in pixels */\nconst MIN_HOUR_HEIGHT = 48;\n\n/** Width of the time axis column in pixels (4rem = 64px) */\nexport const TIME_AXIS_WIDTH = 64;\n\n/** Number of visible days per view mode */\nconst VISIBLE_DAYS_BY_VIEW: Record<ViewType, number> = {\n  day: 1,\n  week: 7,\n  month: 7,\n};\n\n/** Buffer days per view mode (each side, for horizontal scroll) */\nconst BUFFER_DAYS_BY_VIEW: Record<ViewType, number> = {\n  day: 1,\n  week: 7,\n  month: 7,\n};\n\n/** Buffer extension step size per view mode */\nconst BUFFER_STEP_BY_VIEW: Record<ViewType, number> = {\n  day: 1,\n  week: 7,\n  month: 7,\n};\n\n/**\n * Generates an array of WeekDay objects starting from the given date.\n * Note: isToday is computed dynamically, not cached, to handle overnight page views\n */\nfunction generateWeekDays(\n  startDate: Date,\n  count: number,\n): Omit<WeekDay, \"isToday\">[] {\n  const end = addDays(startDate, count - 1);\n\n  return eachDayOfInterval({ start: startDate, end }).map((date) => ({\n    date,\n    dayName: format(date, \"EEE\"),\n    dayNumber: date.getDate(),\n  }));\n}\n\n/**\n * Generates an extended array of days including buffer days on both sides\n * for smooth horizontal scroll transitions\n */\nfunction generateBufferedDays(\n  startDate: Date,\n  bufferDays: number,\n  visibleDays: number,\n): Omit<WeekDay, \"isToday\">[] {\n  const bufferStart = addDays(startDate, -bufferDays);\n  const bufferEnd = addDays(startDate, visibleDays + bufferDays - 1);\n\n  return eachDayOfInterval({ start: bufferStart, end: bufferEnd }).map(\n    (date) => ({\n      date,\n      dayName: format(date, \"EEE\"),\n      dayNumber: date.getDate(),\n    }),\n  );\n}\n\n/**\n * Generates an array of HourSlot objects for all 24 hours\n */\nfunction generateHours(): HourSlot[] {\n  return Array.from({ length: 24 }, (_, i) => {\n    const dateWithHour = new Date();\n    dateWithHour.setHours(i, 0, 0, 0);\n    return {\n      hour: i,\n      label: format(dateWithHour, \"h a\"),\n    };\n  });\n}\n\n/**\n * Returns the month name, year, and week number for the current date\n */\nexport function getCalendarHeaderInfo(\n  currentDate: Date,\n  weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6,\n) {\n  return {\n    monthName: format(currentDate, \"MMMM\"),\n    year: format(currentDate, \"yyyy\"),\n    weekNumber: getWeek(currentDate, { weekStartsOn }),\n  };\n}\n\n/**\n * Returns the visible days starting from the given date (used for sidebar highlighting)\n */\nexport function getVisibleDays(\n  currentDate: Date,\n  view: ViewType = \"week\",\n): Date[] {\n  const count = VISIBLE_DAYS_BY_VIEW[view];\n  const end = addDays(currentDate, count - 1);\n  return eachDayOfInterval({ start: currentDate, end });\n}\n\n/**\n * Main Week View calendar component\n * Displays a week grid with time slots and supports horizontal scroll navigation\n */\nexport function WeekView({\n  view = \"week\",\n  currentDate = new Date(),\n  events = [],\n  onEventClick,\n  selectedEventId,\n  onBackgroundClick,\n  onDateChange,\n  onVisibleDaysChange,\n  onEventChange,\n  isSidebarOpen,\n  onDockToSidebar,\n  onClosePopover,\n  onPrevWeek,\n  onNextWeek,\n  className,\n}: WeekViewProps) {\n  const VISIBLE_DAYS = VISIBLE_DAYS_BY_VIEW[view];\n  const BUFFER_DAYS = BUFFER_DAYS_BY_VIEW[view];\n  const BUFFER_STEP = BUFFER_STEP_BY_VIEW[view];\n  const scrollContainerRef = React.useRef<HTMLDivElement>(null);\n  const dayColumnsScrollRef = React.useRef<HTMLDivElement>(null);\n  const allDayScrollRef = React.useRef<HTMLDivElement>(null);\n  const allDayScrollContentRef = React.useRef<HTMLDivElement>(null);\n\n  // Visible days starting from currentDate\n  const baseDays = React.useMemo(\n    () => generateWeekDays(currentDate, VISIBLE_DAYS),\n    [currentDate, VISIBLE_DAYS],\n  );\n\n  const days: WeekDay[] = baseDays.map((day) => ({\n    ...day,\n    isToday: isToday(day.date),\n  }));\n\n  const hours = React.useMemo(() => generateHours(), []);\n\n  const allDayEvents = React.useMemo(\n    () => events.filter((e) => e.isAllDay || isMultiDayEvent(e)),\n    [events],\n  );\n\n  const timedEvents = React.useMemo(\n    () => events.filter((e) => !e.isAllDay && !isMultiDayEvent(e)),\n    [events],\n  );\n\n  // Compute day column width and dynamic hour height from container\n  const [dayColumnWidth, setDayColumnWidth] = React.useState(0);\n  const [hourHeight, setHourHeight] = React.useState(MIN_HOUR_HEIGHT);\n  const [contextMenuOpen, setContextMenuOpen] = React.useState(false);\n  const [isAllDayResizing, setIsAllDayResizing] = React.useState(false);\n\n  React.useEffect(() => {\n    const updateDimensions = () => {\n      const container = scrollContainerRef.current;\n      if (!container) return;\n      const availableWidth = container.clientWidth - TIME_AXIS_WIDTH;\n      setDayColumnWidth(availableWidth / VISIBLE_DAYS);\n      setHourHeight(Math.max(MIN_HOUR_HEIGHT, container.clientHeight / 24));\n    };\n\n    updateDimensions();\n\n    const observer = new ResizeObserver(updateDimensions);\n    if (scrollContainerRef.current) {\n      observer.observe(scrollContainerRef.current);\n    }\n    return () => observer.disconnect();\n  }, [VISIBLE_DAYS]);\n\n  // Track whether navigation was initiated by scroll (to avoid double-animation)\n  const scrollNavigatedRef = React.useRef(false);\n  const prevDateRef = React.useRef(currentDate);\n\n  const handleNavigate = React.useCallback(\n    (daysDelta: number) => {\n      scrollNavigatedRef.current = true;\n      onDateChange?.(addDays(currentDate, daysDelta));\n    },\n    [currentDate, onDateChange],\n  );\n\n  const handleDragNavigate = React.useCallback(\n    (daysDelta: number) => {\n      onDateChange?.(addDays(currentDate, daysDelta));\n    },\n    [currentDate, onDateChange],\n  );\n\n  const visibleDayDates = React.useMemo(() => days.map((d) => d.date), [days]);\n\n  const { resizeState, handleResizeMouseDown } = useEventResize({\n    hourHeight,\n    scrollContainerRef,\n    events: timedEvents,\n    days: visibleDayDates,\n    dayColumnWidth,\n    timeAxisWidth: TIME_AXIS_WIDTH,\n    onEventChange,\n    onEventClick,\n    onResizeNavigate: handleDragNavigate,\n  });\n\n  const { dragState, handleEventMouseDown } = useEventDrag({\n    hourHeight,\n    scrollContainerRef,\n    events: timedEvents,\n    days: visibleDayDates,\n    dayColumnWidth,\n    timeAxisWidth: TIME_AXIS_WIDTH,\n    onEventChange,\n    onEventClick,\n    onDragNavigate: handleDragNavigate,\n  });\n\n  const { scrollOffset, slideOffset, isAnimating, triggerSlideAnimation } =\n    useHorizontalScroll({\n      containerRef: scrollContainerRef,\n      dayColumnWidth,\n      onNavigate: handleNavigate,\n      disabled:\n        dragState?.isDragging ||\n        resizeState?.isResizing ||\n        isAllDayResizing ||\n        contextMenuOpen,\n    });\n\n  // Compute how many days the scroll has shifted from center\n  const scrollDaysDelta =\n    dayColumnWidth > 0 ? Math.round(-scrollOffset / dayColumnWidth) : 0;\n\n  // Report visible days to parent in real-time as scroll crosses day boundaries\n  React.useEffect(() => {\n    const start = addDays(currentDate, scrollDaysDelta);\n    const end = addDays(start, VISIBLE_DAYS - 1);\n    onVisibleDaysChange?.(eachDayOfInterval({ start, end }));\n  }, [currentDate, scrollDaysDelta, onVisibleDaysChange, VISIBLE_DAYS]);\n\n  // Dynamic buffer: extends in BUFFER_STEP chunks based on scroll distance\n  const extraScrollDays =\n    dayColumnWidth > 0 && BUFFER_STEP > 0\n      ? Math.ceil(Math.abs(scrollOffset) / dayColumnWidth / BUFFER_STEP) *\n        BUFFER_STEP\n      : 0;\n  const dynamicBuffer = BUFFER_DAYS + extraScrollDays;\n  const totalDays = dynamicBuffer + VISIBLE_DAYS + dynamicBuffer;\n\n  // Extended buffered days for scroll (grows dynamically with scroll distance)\n  const bufferedBaseDays = React.useMemo(\n    () => generateBufferedDays(currentDate, dynamicBuffer, VISIBLE_DAYS),\n    [currentDate, dynamicBuffer, VISIBLE_DAYS],\n  );\n\n  const bufferedDays: WeekDay[] = bufferedBaseDays.map((day) => ({\n    ...day,\n    isToday: isToday(day.date),\n  }));\n\n  const bufferedDayDates = React.useMemo(\n    () => bufferedBaseDays.map((d) => d.date),\n    [bufferedBaseDays],\n  );\n\n  const { allDayResizeState, handleAllDayResizeMouseDown } = useAllDayResize({\n    days: bufferedDayDates,\n    dayColumnWidth,\n    allDayContainerRef: allDayScrollContentRef,\n    events: allDayEvents,\n    onEventChange,\n    onEventClick,\n  });\n\n  React.useEffect(() => {\n    setIsAllDayResizing(allDayResizeState?.isResizing ?? false);\n  }, [allDayResizeState?.isResizing]);\n\n  // Trigger slide animation when currentDate changes externally (not from scroll)\n  React.useEffect(() => {\n    if (scrollNavigatedRef.current) {\n      scrollNavigatedRef.current = false;\n      prevDateRef.current = currentDate;\n      return;\n    }\n\n    const prevDate = prevDateRef.current;\n    const daysDiff = differenceInCalendarDays(currentDate, prevDate);\n    prevDateRef.current = currentDate;\n\n    if (daysDiff === 0) return;\n\n    triggerSlideAnimation(daysDiff);\n  }, [currentDate, triggerSlideAnimation]);\n\n  // The base translateX centers on the visible days (skip dynamicBuffer columns)\n  const baseTranslateX = -(dynamicBuffer * dayColumnWidth);\n  const transformX = baseTranslateX + scrollOffset + slideOffset;\n\n  const scrollStyle: React.CSSProperties = {\n    width: `${(totalDays / VISIBLE_DAYS) * 100}%`,\n    transform: `translateX(${transformX}px)`,\n    transition: isAnimating ? `transform ${200}ms ease-out` : \"none\",\n  };\n\n  // Ref for the popover collision boundary (constrains popovers within the calendar area)\n  const calendarBoundaryRef = React.useRef<HTMLDivElement>(null);\n  // Ref for the header (weekday columns + all-day row) to measure its height for popover top inset\n  const calendarHeaderRef = React.useRef<HTMLDivElement>(null);\n\n  return (\n    <CalendarPopoverBoundaryProvider\n      boundaryRef={calendarBoundaryRef}\n      headerRef={calendarHeaderRef}\n      view={view}\n    >\n      <div\n        ref={calendarBoundaryRef}\n        className={cn(\"flex h-full flex-col\", className)}\n        onClick={(e) => {\n          const target = e.target as HTMLElement;\n          if (target.closest(\"[data-radix-popper-content-wrapper]\")) return;\n          onBackgroundClick?.();\n        }}\n      >\n        {/* Header - day columns and all-day row with synchronized scroll */}\n        <div className=\"flex-shrink-0\">\n          <div\n            ref={(el) => {\n              (\n                dayColumnsScrollRef as React.MutableRefObject<HTMLDivElement | null>\n              ).current = el;\n              (\n                calendarHeaderRef as React.MutableRefObject<HTMLDivElement | null>\n              ).current = el;\n            }}\n            className=\"overflow-hidden\"\n          >\n            <div className=\"flex bg-background\">\n              {/* Timezone label - rendered outside scroll container */}\n              <div className=\"text-muted-foreground flex w-16 flex-shrink-0 items-center justify-end pr-2 text-xxs\">\n                {new Date()\n                  .toLocaleTimeString(\"en-US\", { timeZoneName: \"short\" })\n                  .match(/\\s([A-Z]{2,5})$/)?.[1] ?? \"\"}\n              </div>\n              <div className=\"flex-1 overflow-hidden\">\n                <div style={scrollStyle}>\n                  <WeekViewDayColumns days={bufferedDays} standalone />\n                </div>\n              </div>\n            </div>\n          </div>\n          <div className=\"overflow-hidden\" ref={allDayScrollRef}>\n            <WeekViewAllDayRow\n              days={bufferedDays}\n              allDayEvents={allDayEvents}\n              onEventClick={onEventClick}\n              selectedEventId={selectedEventId}\n              scrollStyle={scrollStyle}\n              allDayResizeState={allDayResizeState ?? undefined}\n              onAllDayResizeMouseDown={handleAllDayResizeMouseDown}\n              onEventChange={onEventChange}\n              onContextMenuOpenChange={setContextMenuOpen}\n              allDayScrollContentRef={allDayScrollContentRef}\n              isSidebarOpen={isSidebarOpen}\n              onDockToSidebar={onDockToSidebar}\n              onClosePopover={onClosePopover}\n              onPrevWeek={onPrevWeek}\n              onNextWeek={onNextWeek}\n              visibleStartIndex={dynamicBuffer}\n              visibleCount={VISIBLE_DAYS}\n              dayColumnWidth={dayColumnWidth}\n            />\n          </div>\n        </div>\n\n        {/* Scrollable grid area \\u2014 also serves as the collision boundary for popovers */}\n        <div\n          ref={scrollContainerRef}\n          className=\"flex-1 overflow-auto scrollbar-hide\"\n        >\n          <div\n            className=\"relative flex\"\n            style={{ height: hours.length * hourHeight }}\n          >\n            <WeekViewTimeAxis hours={hours} hourHeight={hourHeight} />\n            <div className=\"relative flex-1 overflow-hidden\">\n              <div style={scrollStyle}>\n                <WeekViewGrid\n                  days={bufferedDays}\n                  hours={hours}\n                  hourHeight={hourHeight}\n                  events={timedEvents}\n                  onEventClick={onEventClick}\n                  selectedEventId={selectedEventId}\n                  dragState={dragState ?? undefined}\n                  onEventDragMouseDown={handleEventMouseDown}\n                  resizeState={resizeState ?? undefined}\n                  onEventResizeMouseDown={handleResizeMouseDown}\n                  onEventChange={onEventChange}\n                  onContextMenuOpenChange={setContextMenuOpen}\n                  isSidebarOpen={isSidebarOpen}\n                  onDockToSidebar={onDockToSidebar}\n                  onClosePopover={onClosePopover}\n                  onPrevWeek={onPrevWeek}\n                  onNextWeek={onNextWeek}\n                />\n              </div>\n            </div>\n            <WeekViewTimeIndicator\n              days={days}\n              hourHeight={hourHeight}\n              scrollDays={bufferedDays}\n              scrollStyle={scrollStyle}\n              behindSelection={!!selectedEventId}\n            />\n          </div>\n        </div>\n      </div>\n    </CalendarPopoverBoundaryProvider>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/layouts/calendar/week-view.tsx"
    },
    {
      "path": "components/ui/kbd.tsx",
      "content": "import { cn } from \"@/lib/utils\";\n\ninterface KbdProps extends React.ComponentProps<\"kbd\"> {\n  /** \"default\" renders with muted background, \"ghost\" renders with transparent background */\n  variant?: \"default\" | \"ghost\";\n}\n\nfunction Kbd({ className, variant = \"default\", ...props }: KbdProps) {\n  return (\n    <kbd\n      data-slot=\"kbd\"\n      className={cn(\n        \"pointer-events-none inline-flex items-center justify-center text-xs font-medium select-none\",\n        \"[&_svg:not([class*='size-'])]:size-3\",\n        variant === \"default\" &&\n          \"h-5 w-fit min-w-5 gap-1 rounded-sm px-1 font-sans bg-muted text-muted-foreground [[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10\",\n        variant === \"ghost\" && \"font-sans text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction KbdGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <kbd\n      data-slot=\"kbd-group\"\n      className={cn(\"inline-flex items-center gap-1\", className)}\n      {...props}\n    />\n  );\n}\n\nexport { Kbd, KbdGroup };\n",
      "type": "registry:ui",
      "target": "components/ui/kbd.tsx"
    },
    {
      "path": "components/ui/sidebar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { PanelLeftIcon } from \"lucide-react\";\n\nimport { useIsMobile } from \"@/hooks/layouts/use-mobile\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"@/components/ui/sheet\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"18rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\";\nconst SIDEBAR_WIDTH_ICON = \"3rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\n\ntype SidebarContextProps = {\n  state: \"expanded\" | \"collapsed\";\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === \"function\" ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open],\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen, setOpenMobile]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? \"expanded\" : \"collapsed\";\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <TooltipProvider delayDuration={1000}>\n        <div\n          data-slot=\"sidebar-wrapper\"\n          style={\n            {\n              \"--sidebar-width\": SIDEBAR_WIDTH,\n              \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n              ...style,\n            } as React.CSSProperties\n          }\n          className={cn(\n            \"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full\",\n            className,\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </TooltipProvider>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = \"left\",\n  variant = \"sidebar\",\n  collapsible = \"offcanvas\",\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  side?: \"left\" | \"right\";\n  variant?: \"sidebar\" | \"floating\" | \"inset\";\n  collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === \"none\") {\n    return (\n      <div\n        data-slot=\"sidebar\"\n        className={cn(\n          \"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n        <SheetContent\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          data-mobile=\"true\"\n          className=\"bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden\"\n          style={\n            {\n              \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n          side={side}\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer text-sidebar-foreground hidden md:block\"\n      data-state={state}\n      data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n      data-variant={variant}\n      data-side={side}\n      data-slot=\"sidebar\"\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        data-slot=\"sidebar-gap\"\n        className={cn(\n          \"relative w-(--sidebar-width) bg-transparent\",\n          \"group-data-[collapsible=offcanvas]:w-0\",\n          \"group-data-[side=right]:rotate-180\",\n          variant === \"floating\" || variant === \"inset\"\n            ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]\"\n            : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\",\n        )}\n      />\n      <div\n        data-slot=\"sidebar-container\"\n        className={cn(\n          \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) md:flex\",\n          side === \"left\"\n            ? \"left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]\"\n            : \"right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]\",\n          // Adjust the padding for floating and inset variants.\n          variant === \"floating\" || variant === \"inset\"\n            ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\n            : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l\",\n          className,\n        )}\n        {...props}\n      >\n        <div\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n          className=\"bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      variant=\"ghost\"\n      size=\"icon\"\n      className={cn(\"size-7\", className)}\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<\"button\">) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      aria-label=\"Toggle Sidebar\"\n      tabIndex={-1}\n      onClick={toggleSidebar}\n      title=\"Toggle Sidebar\"\n      className={cn(\n        \"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex\",\n        \"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize\",\n        \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\n        \"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full\",\n        \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n        \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<\"main\">) {\n  return (\n    <main\n      data-slot=\"sidebar-inset\"\n      className={cn(\n        \"bg-background relative flex w-full flex-1 flex-col\",\n        \"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      data-slot=\"sidebar-input\"\n      data-sidebar=\"input\"\n      className={cn(\"bg-background h-8 w-full shadow-none\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      data-sidebar=\"header\"\n      className={cn(\"flex flex-col gap-2 p-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      data-sidebar=\"footer\"\n      className={cn(\"flex flex-col gap-2 p-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"sidebar-separator\"\n      data-sidebar=\"separator\"\n      className={cn(\"bg-sidebar-border mx-2 w-auto\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      data-sidebar=\"content\"\n      className={cn(\n        \"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-group\"\n      data-sidebar=\"group\"\n      className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"div\"> & { asChild?: boolean }) {\n  const Comp = (asChild ? Slot : \"div\") as React.ElementType;\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-label\"\n      data-sidebar=\"group-label\"\n      className={cn(\n        \"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-sm px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        \"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupAction({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> & { asChild?: boolean }) {\n  const Comp = (asChild ? Slot : \"button\") as React.ElementType;\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-action\"\n      data-sidebar=\"group-action\"\n      className={cn(\n        \"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-sm p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        // Increases the hit area of the button on mobile.\n        \"after:absolute after:-inset-2 md:after:hidden\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-group-content\"\n      data-sidebar=\"group-content\"\n      className={cn(\"w-full text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      data-sidebar=\"menu\"\n      className={cn(\"flex w-full min-w-0 flex-col gap-1\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      data-sidebar=\"menu-item\"\n      className={cn(\"group/menu-item relative\", className)}\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  \"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-sm p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n        outline:\n          \"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]\",\n      },\n      size: {\n        default: \"h-8 text-sm\",\n        sm: \"h-7 text-xs\",\n        lg: \"h-12 text-sm group-data-[collapsible=icon]:p-0!\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  },\n);\n\nfunction SidebarMenuButton({\n  asChild = false,\n  isActive = false,\n  variant = \"default\",\n  size = \"default\",\n  tooltip,\n  className,\n  ...props\n}: React.ComponentProps<\"button\"> & {\n  asChild?: boolean;\n  isActive?: boolean;\n  tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n} & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const Comp = (asChild ? Slot : \"button\") as React.ElementType;\n  const { isMobile, state } = useSidebar();\n\n  const button = (\n    <Comp\n      data-slot=\"sidebar-menu-button\"\n      data-sidebar=\"menu-button\"\n      data-size={size}\n      data-active={isActive}\n      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n      {...props}\n    />\n  );\n\n  if (!tooltip) {\n    return button;\n  }\n\n  if (typeof tooltip === \"string\") {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>{button}</TooltipTrigger>\n      <TooltipContent\n        side=\"right\"\n        align=\"center\"\n        hidden={state !== \"collapsed\" || isMobile}\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  asChild = false,\n  showOnHover = false,\n  ...props\n}: React.ComponentProps<\"button\"> & {\n  asChild?: boolean;\n  showOnHover?: boolean;\n}) {\n  const Comp = (asChild ? Slot : \"button\") as React.ElementType;\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-action\"\n      data-sidebar=\"menu-action\"\n      className={cn(\n        \"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-sm p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        // Increases the hit area of the button on mobile.\n        \"after:absolute after:-inset-2 md:after:hidden\",\n        \"peer-data-[size=sm]/menu-button:top-1\",\n        \"peer-data-[size=default]/menu-button:top-1.5\",\n        \"peer-data-[size=lg]/menu-button:top-2.5\",\n        \"group-data-[collapsible=icon]:hidden\",\n        showOnHover &&\n          \"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-badge\"\n      data-sidebar=\"menu-badge\"\n      className={cn(\n        \"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-sm px-1 text-xs font-medium tabular-nums select-none\",\n        \"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground\",\n        \"peer-data-[size=sm]/menu-button:top-1\",\n        \"peer-data-[size=default]/menu-button:top-1.5\",\n        \"peer-data-[size=lg]/menu-button:top-2.5\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  showIcon?: boolean;\n}) {\n  // Stable pseudo-random width between 50 to 90%.\n  const [width] = React.useState(\n    () => `${Math.floor(Math.random() * 40) + 50}%`,\n  );\n\n  return (\n    <div\n      data-slot=\"sidebar-menu-skeleton\"\n      data-sidebar=\"menu-skeleton\"\n      className={cn(\"flex h-8 items-center gap-2 rounded-sm px-2\", className)}\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-sm\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            \"--skeleton-width\": width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu-sub\"\n      data-sidebar=\"menu-sub\"\n      className={cn(\n        \"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-sub-item\"\n      data-sidebar=\"menu-sub-item\"\n      className={cn(\"group/menu-sub-item relative\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  asChild = false,\n  size = \"md\",\n  isActive = false,\n  className,\n  ...props\n}: React.ComponentProps<\"a\"> & {\n  asChild?: boolean;\n  size?: \"sm\" | \"md\";\n  isActive?: boolean;\n}) {\n  const Comp = (asChild ? Slot : \"a\") as React.ElementType;\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-sub-button\"\n      data-sidebar=\"menu-sub-button\"\n      data-size={size}\n      data-active={isActive}\n      className={cn(\n        \"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-sm px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n        \"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground\",\n        size === \"sm\" && \"text-xs\",\n        size === \"md\" && \"text-sm\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/sidebar.tsx"
    },
    {
      "path": "components/mode-toggle.tsx",
      "content": "\"use client\";\n\nimport { useState, useEffect, useRef, useId } from \"react\";\nimport { useTheme } from \"next-themes\";\nimport { motion } from \"motion/react\";\n\n/* ── Audio ── */\n\nlet _ctx: AudioContext | null = null;\nlet _buf: AudioBuffer | null = null;\n\nfunction audioCtx() {\n  if (!_ctx) {\n    _ctx = new (window.AudioContext ||\n      (window as unknown as { webkitAudioContext: typeof AudioContext })\n        .webkitAudioContext)();\n  }\n  if (_ctx.state === \"suspended\") _ctx.resume();\n  return _ctx;\n}\n\nfunction ensureBuf(ac: AudioContext): AudioBuffer {\n  if (_buf && _buf.sampleRate === ac.sampleRate) return _buf;\n  const rate = ac.sampleRate;\n  const len = Math.floor(rate * 0.006);\n  const buf = ac.createBuffer(1, len, rate);\n  const ch = buf.getChannelData(0);\n  for (let i = 0; i < len; i++) {\n    const t = i / len;\n    const sine = Math.sin(2 * Math.PI * 3400 * t);\n    const noise = Math.random() * 2 - 1;\n    ch[i] = (sine * 0.6 + noise * 0.4) * (1 - t) ** 3;\n  }\n  _buf = buf;\n  return buf;\n}\n\nfunction tick(last: React.MutableRefObject<number>) {\n  const now = performance.now();\n  if (now - last.current < 80) return;\n  last.current = now;\n  try {\n    const ac = audioCtx();\n    const buf = ensureBuf(ac);\n    const src = ac.createBufferSource();\n    const gain = ac.createGain();\n    src.buffer = buf;\n    gain.gain.value = 0.08;\n    src.connect(gain);\n    gain.connect(ac.destination);\n    src.start();\n  } catch {\n    /* silent */\n  }\n}\n\n/* ── Component ── */\n\nexport function ModeToggle() {\n  const { resolvedTheme, setTheme } = useTheme();\n  const rawId = useId();\n  const maskId = `mt${rawId.replace(/:/g, \"\")}`;\n  const lastSnd = useRef(0);\n  const isFirst = useRef(true);\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    setMounted(true);\n    requestAnimationFrame(() => {\n      isFirst.current = false;\n    });\n  }, []);\n\n  const isDark = resolvedTheme === \"dark\";\n\n  const toggle = () => {\n    setTheme(isDark ? \"light\" : \"dark\");\n    tick(lastSnd);\n  };\n\n  /* Placeholder during SSR to avoid layout shift */\n  if (!mounted) {\n    return <div style={{ width: 32, height: 32 }} />;\n  }\n\n  const spring = isFirst.current\n    ? { duration: 0 }\n    : { type: \"spring\" as const, stiffness: 380, damping: 30 };\n\n  return (\n    <motion.button\n      onClick={toggle}\n      whileHover={{ scale: 1.1 }}\n      whileTap={{ scale: 0.86 }}\n      transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n      style={{\n        background: \"none\",\n        border: \"none\",\n        cursor: \"pointer\",\n        padding: 6,\n        width: 32,\n        height: 32,\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        color: \"currentColor\",\n        borderRadius: 8,\n        outline: \"none\",\n        WebkitTapHighlightColor: \"transparent\",\n      }}\n      aria-label=\"Toggle theme\"\n    >\n      <motion.svg\n        width=\"18\"\n        height=\"18\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        initial={false}\n        animate={{ rotate: isDark ? 270 : 0 }}\n        transition={spring}\n        style={{ overflow: \"visible\" }}\n      >\n        <mask id={maskId}>\n          <rect x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" fill=\"white\" />\n          <motion.circle\n            initial={false}\n            animate={{ cx: isDark ? 17 : 33, cy: isDark ? 8 : 0 }}\n            transition={spring}\n            r=\"9\"\n            fill=\"black\"\n          />\n        </mask>\n\n        <motion.circle\n          cx=\"12\"\n          cy=\"12\"\n          fill=\"currentColor\"\n          stroke=\"none\"\n          mask={`url(#${maskId})`}\n          initial={false}\n          animate={{ r: isDark ? 9 : 5 }}\n          transition={spring}\n        />\n\n        <motion.g\n          initial={false}\n          animate={{\n            opacity: isDark ? 0 : 1,\n            scale: isDark ? 0 : 1,\n            rotate: isDark ? -30 : 0,\n          }}\n          transition={spring}\n          style={{ transformOrigin: \"12px 12px\" }}\n        >\n          <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n          <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n          <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n          <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n          <line x1=\"5.64\" y1=\"5.64\" x2=\"4.22\" y2=\"4.22\" />\n          <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n          <line x1=\"5.64\" y1=\"18.36\" x2=\"4.22\" y2=\"19.78\" />\n          <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n        </motion.g>\n      </motion.svg>\n    </motion.button>\n  );\n}\n\nModeToggle.displayName = \"ModeToggle\";\n",
      "type": "registry:component",
      "target": "components/mode-toggle.tsx"
    },
    {
      "path": "hooks/layouts/use-all-day-resize.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type {\n  AllDayResizeState,\n  CalendarEvent,\n} from \"@/components/layouts/calendar/week-view-types\";\n\ninterface UseAllDayResizeOptions {\n  days: Date[];\n  dayColumnWidth: number;\n  allDayContainerRef: React.RefObject<HTMLDivElement | null>;\n  events: CalendarEvent[];\n  onEventChange?: (event: CalendarEvent) => void;\n  onEventClick?: (event: CalendarEvent) => void;\n}\n\ninterface UseAllDayResizeReturn {\n  allDayResizeState: AllDayResizeState | null;\n  handleAllDayResizeMouseDown: (\n    e: React.MouseEvent,\n    event: CalendarEvent,\n    edge: \"left\" | \"right\" | \"move\",\n    startColumn: number,\n    endColumn: number,\n  ) => void;\n}\n\nconst DRAG_THRESHOLD_PX = 4;\n\nfunction clamp(value: number, min: number, max: number): number {\n  if (value < min) return min;\n  if (value > max) return max;\n  return value;\n}\n\ninterface ResizeInfo {\n  eventId: string;\n  event: CalendarEvent;\n  edge: \"left\" | \"right\" | \"move\";\n  startClientX: number;\n  isResizing: boolean;\n  originalStartColumn: number;\n  originalEndColumn: number;\n  /** Column index at the initial mousedown position (used for move delta) */\n  startColumnIndex: number;\n  /** Offset from cursor to event left edge at mousedown (px) */\n  cursorOffsetX: number;\n  /** Offset from cursor to event top edge at mousedown (px) */\n  cursorOffsetY: number;\n}\n\nexport function useAllDayResize({\n  days,\n  dayColumnWidth,\n  allDayContainerRef,\n  events,\n  onEventChange,\n  onEventClick,\n}: UseAllDayResizeOptions): UseAllDayResizeReturn {\n  const [allDayResizeState, setAllDayResizeState] =\n    useState<AllDayResizeState | null>(null);\n\n  const resizeRef = useRef<ResizeInfo | null>(null);\n  const onEventChangeRef = useRef(onEventChange);\n  const onEventClickRef = useRef(onEventClick);\n  const eventsRef = useRef(events);\n  const daysRef = useRef(days);\n  const dayColumnWidthRef = useRef(dayColumnWidth);\n\n  useEffect(() => {\n    onEventChangeRef.current = onEventChange;\n  }, [onEventChange]);\n  useEffect(() => {\n    onEventClickRef.current = onEventClick;\n  }, [onEventClick]);\n  useEffect(() => {\n    eventsRef.current = events;\n  }, [events]);\n  useEffect(() => {\n    daysRef.current = days;\n  }, [days]);\n  useEffect(() => {\n    dayColumnWidthRef.current = dayColumnWidth;\n  }, [dayColumnWidth]);\n\n  const handleMouseMoveRef = useRef<((e: MouseEvent) => void) | null>(null);\n  const handleMouseUpRef = useRef<(() => void) | null>(null);\n\n  const cleanup = useCallback(() => {\n    if (handleMouseMoveRef.current) {\n      window.removeEventListener(\"mousemove\", handleMouseMoveRef.current);\n    }\n    if (handleMouseUpRef.current) {\n      window.removeEventListener(\"mouseup\", handleMouseUpRef.current);\n    }\n    document.body.style.cursor = \"\";\n  }, []);\n\n  useEffect(() => {\n    handleMouseMoveRef.current = (e: MouseEvent) => {\n      const resize = resizeRef.current;\n      if (!resize) return;\n\n      const deltaX = Math.abs(e.clientX - resize.startClientX);\n      if (!resize.isResizing && deltaX < DRAG_THRESHOLD_PX) return;\n\n      if (!resize.isResizing) {\n        resize.isResizing = true;\n        document.body.style.cursor =\n          resize.edge === \"move\" ? \"grabbing\" : \"col-resize\";\n      }\n\n      const container = allDayContainerRef.current;\n      if (!container) return;\n\n      const rect = container.getBoundingClientRect();\n      const colWidth = dayColumnWidthRef.current;\n      const currentDays = daysRef.current;\n      const columnIndex = clamp(\n        Math.floor((e.clientX - rect.left) / colWidth),\n        0,\n        currentDays.length - 1,\n      );\n\n      let newStartColumn = resize.originalStartColumn;\n      let newEndColumn = resize.originalEndColumn;\n\n      if (resize.edge === \"move\") {\n        const span = resize.originalEndColumn - resize.originalStartColumn;\n        const delta = columnIndex - resize.startColumnIndex;\n        newStartColumn = clamp(\n          resize.originalStartColumn + delta,\n          0,\n          currentDays.length - 1 - span,\n        );\n        newEndColumn = newStartColumn + span;\n      } else if (resize.edge === \"right\") {\n        newEndColumn = Math.max(columnIndex, resize.originalStartColumn);\n      } else {\n        newStartColumn = Math.min(columnIndex, resize.originalEndColumn);\n      }\n\n      setAllDayResizeState({\n        eventId: resize.eventId,\n        event: resize.event,\n        originalStartColumn: resize.originalStartColumn,\n        originalEndColumn: resize.originalEndColumn,\n        currentStartColumn: newStartColumn,\n        currentEndColumn: newEndColumn,\n        edge: resize.edge,\n        isResizing: true,\n        ...(resize.edge === \"move\"\n          ? {\n              clientX: e.clientX,\n              clientY: e.clientY,\n              cursorOffsetX: resize.cursorOffsetX,\n              cursorOffsetY: resize.cursorOffsetY,\n            }\n          : {}),\n      });\n    };\n\n    handleMouseUpRef.current = () => {\n      const resize = resizeRef.current;\n      if (!resize) return;\n\n      cleanup();\n\n      if (resize.isResizing) {\n        setAllDayResizeState((prev) => {\n          if (!prev) return null;\n\n          const event = eventsRef.current.find((e) => e.id === resize.eventId);\n          if (!event) return null;\n\n          const currentDays = daysRef.current;\n          const newStartDate = currentDays[prev.currentStartColumn];\n          const newEndDate = currentDays[prev.currentEndColumn];\n\n          if (!newStartDate || !newEndDate) return null;\n\n          // Preserve time-of-day from original event\n          const newStart = new Date(newStartDate);\n          newStart.setHours(\n            event.start.getHours(),\n            event.start.getMinutes(),\n            event.start.getSeconds(),\n            event.start.getMilliseconds(),\n          );\n\n          const newEnd = new Date(newEndDate);\n          newEnd.setHours(\n            event.end.getHours(),\n            event.end.getMinutes(),\n            event.end.getSeconds(),\n            event.end.getMilliseconds(),\n          );\n\n          // Move preserves the original isAllDay flag (duration unchanged).\n          // Resize determines isAllDay by whether the span exceeds 24h.\n          const isMove = resize.edge === \"move\";\n          const MS_IN_24H = 24 * 60 * 60 * 1000;\n          const isLongerThan24h =\n            newEnd.getTime() - newStart.getTime() > MS_IN_24H;\n          const isAllDay = isMove ? event.isAllDay === true : isLongerThan24h;\n\n          onEventChangeRef.current?.({\n            ...event,\n            start: newStart,\n            end: newEnd,\n            isAllDay,\n          });\n\n          return null;\n        });\n      } else {\n        setAllDayResizeState(null);\n      }\n\n      resizeRef.current = null;\n    };\n  }, [allDayContainerRef, cleanup]);\n\n  const handleAllDayResizeMouseDown = useCallback(\n    (\n      e: React.MouseEvent,\n      event: CalendarEvent,\n      edge: \"left\" | \"right\" | \"move\",\n      startColumn: number,\n      endColumn: number,\n    ) => {\n      if (e.button !== 0) return;\n\n      e.stopPropagation();\n\n      onEventClickRef.current?.(event);\n\n      // Compute the column under the cursor at mousedown for move delta\n      const container = allDayContainerRef.current;\n      let startColumnIndex = startColumn;\n      let cursorOffsetX = 0;\n      const cursorOffsetY = 0;\n      if (container) {\n        const rect = container.getBoundingClientRect();\n        const colWidth = dayColumnWidthRef.current;\n        startColumnIndex = clamp(\n          Math.floor((e.clientX - rect.left) / colWidth),\n          0,\n          daysRef.current.length - 1,\n        );\n        // Compute offset from cursor to the event element's top-left\n        const eventLeft = rect.left + startColumn * colWidth;\n        cursorOffsetX = e.clientX - eventLeft;\n      }\n\n      resizeRef.current = {\n        eventId: event.id,\n        event,\n        edge,\n        startClientX: e.clientX,\n        isResizing: false,\n        originalStartColumn: startColumn,\n        originalEndColumn: endColumn,\n        startColumnIndex,\n        cursorOffsetX,\n        cursorOffsetY,\n      };\n\n      setAllDayResizeState({\n        eventId: event.id,\n        event,\n        originalStartColumn: startColumn,\n        originalEndColumn: endColumn,\n        currentStartColumn: startColumn,\n        currentEndColumn: endColumn,\n        edge,\n        isResizing: false,\n      });\n\n      if (handleMouseMoveRef.current) {\n        window.addEventListener(\"mousemove\", handleMouseMoveRef.current);\n      }\n      if (handleMouseUpRef.current) {\n        window.addEventListener(\"mouseup\", handleMouseUpRef.current);\n      }\n    },\n    [allDayContainerRef],\n  );\n\n  useEffect(() => {\n    return cleanup;\n  }, [cleanup]);\n\n  return { allDayResizeState, handleAllDayResizeMouseDown };\n}\n",
      "type": "registry:hook",
      "target": "hooks/layouts/use-all-day-resize.ts"
    },
    {
      "path": "hooks/layouts/use-event-drag.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type {\n  CalendarEvent,\n  EventDragState,\n} from \"@/components/layouts/calendar/week-view-types\";\n\ninterface UseEventDragOptions {\n  hourHeight: number;\n  scrollContainerRef: React.RefObject<HTMLDivElement | null>;\n  events: CalendarEvent[];\n  days: Date[];\n  dayColumnWidth: number;\n  timeAxisWidth: number;\n  onEventChange?: (event: CalendarEvent) => void;\n  onEventClick?: (event: CalendarEvent) => void;\n  onDragNavigate?: (daysDelta: number) => void;\n}\n\ninterface UseEventDragReturn {\n  dragState: EventDragState | null;\n  handleEventMouseDown: (e: React.MouseEvent, event: CalendarEvent) => void;\n}\n\nconst DRAG_THRESHOLD_PX = 4;\nconst SNAP_MINUTES = 15;\nconst EDGE_ZONE_PX = 40;\nconst EDGE_NAV_DELAY_MS = 500;\nconst EDGE_NAV_REPEAT_MS = 800;\nconst AUTO_SCROLL_ZONE_PX = 60;\nconst AUTO_SCROLL_MAX_SPEED = 12;\n\nfunction snapToGrid(minutes: number): number {\n  return Math.round(minutes / SNAP_MINUTES) * SNAP_MINUTES;\n}\n\nfunction addMinutesToDate(date: Date, minutes: number): Date {\n  const result = new Date(date);\n  result.setHours(0, 0, 0, 0);\n  result.setMinutes(minutes);\n  return result;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n  if (value < min) return min;\n  if (value > max) return max;\n  return value;\n}\n\ninterface DragInfo {\n  eventId: string;\n  event: CalendarEvent;\n  startClientY: number;\n  startClientX: number;\n  offsetWithinEvent: number;\n  offsetWithinEventX: number;\n  isDragging: boolean;\n  durationMinutes: number;\n}\n\nexport function useEventDrag({\n  hourHeight,\n  scrollContainerRef,\n  events,\n  days,\n  dayColumnWidth,\n  timeAxisWidth,\n  onEventChange,\n  onEventClick,\n  onDragNavigate,\n}: UseEventDragOptions): UseEventDragReturn {\n  const [dragState, setDragState] = useState<EventDragState | null>(null);\n\n  const dragRef = useRef<DragInfo | null>(null);\n  const onEventChangeRef = useRef(onEventChange);\n  const onEventClickRef = useRef(onEventClick);\n  const onDragNavigateRef = useRef(onDragNavigate);\n  const eventsRef = useRef(events);\n  const hourHeightRef = useRef(hourHeight);\n  const daysRef = useRef(days);\n  const dayColumnWidthRef = useRef(dayColumnWidth);\n  const timeAxisWidthRef = useRef(timeAxisWidth);\n\n  useEffect(() => {\n    onEventChangeRef.current = onEventChange;\n  }, [onEventChange]);\n  useEffect(() => {\n    onEventClickRef.current = onEventClick;\n  }, [onEventClick]);\n  useEffect(() => {\n    onDragNavigateRef.current = onDragNavigate;\n  }, [onDragNavigate]);\n  useEffect(() => {\n    eventsRef.current = events;\n  }, [events]);\n  useEffect(() => {\n    hourHeightRef.current = hourHeight;\n  }, [hourHeight]);\n  useEffect(() => {\n    daysRef.current = days;\n  }, [days]);\n  useEffect(() => {\n    dayColumnWidthRef.current = dayColumnWidth;\n  }, [dayColumnWidth]);\n  useEffect(() => {\n    timeAxisWidthRef.current = timeAxisWidth;\n  }, [timeAxisWidth]);\n\n  // Edge navigation timer refs\n  const edgeNavTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const edgeNavDirectionRef = useRef<number | null>(null);\n\n  // Auto-scroll RAF ref\n  const autoScrollRAFRef = useRef<number | null>(null);\n  const autoScrollSpeedRef = useRef(0);\n\n  // Store handlers in refs to break the circular dependency\n  const handleMouseMoveRef = useRef<((e: MouseEvent) => void) | null>(null);\n  const handleMouseUpRef = useRef<(() => void) | null>(null);\n\n  const cancelEdgeNav = useCallback(() => {\n    if (edgeNavTimerRef.current !== null) {\n      clearTimeout(edgeNavTimerRef.current);\n      edgeNavTimerRef.current = null;\n    }\n    edgeNavDirectionRef.current = null;\n  }, []);\n\n  const cancelAutoScroll = useCallback(() => {\n    if (autoScrollRAFRef.current !== null) {\n      cancelAnimationFrame(autoScrollRAFRef.current);\n      autoScrollRAFRef.current = null;\n    }\n    autoScrollSpeedRef.current = 0;\n  }, []);\n\n  const cleanup = useCallback(() => {\n    if (handleMouseMoveRef.current) {\n      window.removeEventListener(\"mousemove\", handleMouseMoveRef.current);\n    }\n    if (handleMouseUpRef.current) {\n      window.removeEventListener(\"mouseup\", handleMouseUpRef.current);\n    }\n    cancelEdgeNav();\n    cancelAutoScroll();\n  }, [cancelEdgeNav, cancelAutoScroll]);\n\n  // Auto-scroll loop\n  const startAutoScrollLoop = useCallback(() => {\n    if (autoScrollRAFRef.current !== null) return;\n\n    const tick = () => {\n      const container = scrollContainerRef.current;\n      if (!container) return;\n\n      const speed = autoScrollSpeedRef.current;\n      if (speed === 0) {\n        autoScrollRAFRef.current = null;\n        return;\n      }\n\n      container.scrollTop += speed;\n      autoScrollRAFRef.current = requestAnimationFrame(tick);\n    };\n\n    autoScrollRAFRef.current = requestAnimationFrame(tick);\n  }, [scrollContainerRef]);\n\n  const scheduleEdgeNav = useCallback(\n    (direction: number) => {\n      if (edgeNavDirectionRef.current === direction) return;\n\n      cancelEdgeNav();\n      edgeNavDirectionRef.current = direction;\n\n      const fireNav = () => {\n        onDragNavigateRef.current?.(direction);\n        edgeNavTimerRef.current = setTimeout(fireNav, EDGE_NAV_REPEAT_MS);\n      };\n\n      edgeNavTimerRef.current = setTimeout(fireNav, EDGE_NAV_DELAY_MS);\n    },\n    [cancelEdgeNav],\n  );\n\n  // Initialize the handlers once (stable references via refs)\n  useEffect(() => {\n    handleMouseMoveRef.current = (e: MouseEvent) => {\n      const drag = dragRef.current;\n      if (!drag) return;\n\n      const deltaY = Math.abs(e.clientY - drag.startClientY);\n      const deltaX = Math.abs(e.clientX - drag.startClientX);\n      if (\n        !drag.isDragging &&\n        deltaY < DRAG_THRESHOLD_PX &&\n        deltaX < DRAG_THRESHOLD_PX\n      )\n        return;\n\n      if (!drag.isDragging) {\n        drag.isDragging = true;\n      }\n\n      const container = scrollContainerRef.current;\n      if (!container) return;\n\n      const containerRect = container.getBoundingClientRect();\n      const scrollTop = container.scrollTop;\n      const absoluteY =\n        e.clientY - containerRect.top + scrollTop - drag.offsetWithinEvent;\n      const absoluteX =\n        e.clientX - containerRect.left - drag.offsetWithinEventX;\n      const rawMinutes = (absoluteY / hourHeightRef.current) * 60;\n      const snappedStartMinutes = snapToGrid(rawMinutes);\n      const clampedStart = Math.max(\n        0,\n        Math.min(snappedStartMinutes, 1440 - drag.durationMinutes),\n      );\n\n      // Column detection — based on raw cursor position over the grid\n      const colWidth = dayColumnWidthRef.current;\n      const visibleDays = daysRef.current;\n      const gridLeftEdge = containerRect.left + timeAxisWidthRef.current;\n      const cursorInGrid = e.clientX - gridLeftEdge;\n      const columnIndex = clamp(\n        Math.floor(cursorInGrid / colWidth),\n        0,\n        visibleDays.length - 1,\n      );\n      const targetDay = visibleDays[columnIndex];\n\n      const currentStart = addMinutesToDate(targetDay, clampedStart);\n      const currentEnd = addMinutesToDate(\n        targetDay,\n        clampedStart + drag.durationMinutes,\n      );\n\n      setDragState({\n        eventId: drag.eventId,\n        event: drag.event,\n        originalStart: drag.event.start,\n        originalEnd: drag.event.end,\n        currentStart,\n        currentEnd,\n        currentDate: targetDay,\n        isDragging: true,\n        cursorY: absoluteY,\n        cursorX: absoluteX,\n        clientX: e.clientX - drag.offsetWithinEventX,\n        clientY: e.clientY - drag.offsetWithinEvent,\n      });\n\n      // Edge-of-view week navigation\n      // Trigger when cursor is within EDGE_ZONE_PX of the grid boundary OR past it\n      const cursorXInGrid = e.clientX - gridLeftEdge;\n      const gridWidth = colWidth * visibleDays.length;\n\n      if (cursorXInGrid < EDGE_ZONE_PX) {\n        scheduleEdgeNav(-7);\n      } else if (cursorXInGrid > gridWidth - EDGE_ZONE_PX) {\n        scheduleEdgeNav(7);\n      } else {\n        cancelEdgeNav();\n      }\n\n      // Auto-scroll at top/bottom edges\n      const cursorYInContainer = e.clientY - containerRect.top;\n      const containerHeight = containerRect.height;\n\n      if (cursorYInContainer < AUTO_SCROLL_ZONE_PX) {\n        const dist = cursorYInContainer;\n        autoScrollSpeedRef.current =\n          -AUTO_SCROLL_MAX_SPEED * (1 - dist / AUTO_SCROLL_ZONE_PX);\n        startAutoScrollLoop();\n      } else if (cursorYInContainer > containerHeight - AUTO_SCROLL_ZONE_PX) {\n        const dist = containerHeight - cursorYInContainer;\n        autoScrollSpeedRef.current =\n          AUTO_SCROLL_MAX_SPEED * (1 - dist / AUTO_SCROLL_ZONE_PX);\n        startAutoScrollLoop();\n      } else {\n        cancelAutoScroll();\n      }\n    };\n\n    handleMouseUpRef.current = () => {\n      const drag = dragRef.current;\n      if (!drag) return;\n\n      cleanup();\n\n      if (drag.isDragging) {\n        setDragState((prev) => {\n          if (!prev) return null;\n\n          const event = eventsRef.current.find((e) => e.id === drag.eventId);\n          if (!event) return null;\n\n          onEventChangeRef.current?.({\n            ...event,\n            start: prev.currentStart,\n            end: prev.currentEnd,\n          });\n\n          return null;\n        });\n      } else {\n        setDragState(null);\n      }\n\n      dragRef.current = null;\n    };\n  }, [\n    scrollContainerRef,\n    cleanup,\n    scheduleEdgeNav,\n    cancelEdgeNav,\n    cancelAutoScroll,\n    startAutoScrollLoop,\n  ]);\n\n  const handleEventMouseDown = useCallback(\n    (e: React.MouseEvent, event: CalendarEvent) => {\n      if (e.button !== 0) return;\n\n      // Select the event immediately\n      onEventClickRef.current?.(event);\n\n      const container = scrollContainerRef.current;\n      if (!container) return;\n\n      // Compute offset within the event element\n      const target = e.currentTarget as HTMLElement;\n      const targetRect = target.getBoundingClientRect();\n      const offsetWithinEvent = e.clientY - targetRect.top;\n      const offsetWithinEventX = e.clientX - targetRect.left;\n\n      const startMinutes =\n        event.start.getHours() * 60 + event.start.getMinutes();\n      const endMinutes = event.end.getHours() * 60 + event.end.getMinutes();\n      const durationMinutes = endMinutes - startMinutes;\n\n      dragRef.current = {\n        eventId: event.id,\n        event,\n        startClientY: e.clientY,\n        startClientX: e.clientX,\n        offsetWithinEvent,\n        offsetWithinEventX,\n        isDragging: false,\n        durationMinutes,\n      };\n\n      setDragState({\n        eventId: event.id,\n        event,\n        originalStart: event.start,\n        originalEnd: event.end,\n        currentStart: event.start,\n        currentEnd: event.end,\n        currentDate: event.start,\n        isDragging: false,\n        cursorY: 0,\n        cursorX: 0,\n        clientX: 0,\n        clientY: 0,\n      });\n\n      if (handleMouseMoveRef.current) {\n        window.addEventListener(\"mousemove\", handleMouseMoveRef.current);\n      }\n      if (handleMouseUpRef.current) {\n        window.addEventListener(\"mouseup\", handleMouseUpRef.current);\n      }\n    },\n    [scrollContainerRef],\n  );\n\n  // Cleanup on unmount\n  useEffect(() => {\n    return cleanup;\n  }, [cleanup]);\n\n  return { dragState, handleEventMouseDown };\n}\n",
      "type": "registry:hook",
      "target": "hooks/layouts/use-event-drag.ts"
    },
    {
      "path": "hooks/layouts/use-event-resize.ts",
      "content": "\"use client\";\n\nimport { isSameDay, startOfDay } from \"date-fns\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type {\n  CalendarEvent,\n  EventResizeState,\n} from \"@/components/layouts/calendar/week-view-types\";\n\ninterface UseEventResizeOptions {\n  hourHeight: number;\n  scrollContainerRef: React.RefObject<HTMLDivElement | null>;\n  events: CalendarEvent[];\n  days: Date[];\n  dayColumnWidth: number;\n  timeAxisWidth: number;\n  onEventChange?: (event: CalendarEvent) => void;\n  onEventClick?: (event: CalendarEvent) => void;\n  onResizeNavigate?: (daysDelta: number) => void;\n}\n\ninterface UseEventResizeReturn {\n  resizeState: EventResizeState | null;\n  handleResizeMouseDown: (\n    e: React.MouseEvent,\n    event: CalendarEvent,\n    edge: \"top\" | \"bottom\",\n  ) => void;\n}\n\nconst DRAG_THRESHOLD_PX = 4;\nconst SNAP_MINUTES = 15;\nconst MIN_DURATION_MINUTES = 15;\nconst AUTO_SCROLL_ZONE_PX = 60;\nconst AUTO_SCROLL_MAX_SPEED = 12;\nconst EDGE_ZONE_PX = 40;\nconst EDGE_NAV_DELAY_MS = 500;\nconst EDGE_NAV_REPEAT_MS = 800;\n\nfunction snapToGrid(minutes: number): number {\n  return Math.round(minutes / SNAP_MINUTES) * SNAP_MINUTES;\n}\n\nfunction addMinutesToDate(date: Date, minutes: number): Date {\n  const result = new Date(date);\n  result.setHours(0, 0, 0, 0);\n  result.setMinutes(minutes);\n  return result;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n  if (value < min) return min;\n  if (value > max) return max;\n  return value;\n}\n\ninterface ResizeInfo {\n  eventId: string;\n  event: CalendarEvent;\n  edge: \"top\" | \"bottom\";\n  startClientY: number;\n  isResizing: boolean;\n  originalStartMinutes: number;\n  originalEndMinutes: number;\n  originalStartDate: Date;\n  originalEndDate: Date;\n}\n\nexport function useEventResize({\n  hourHeight,\n  scrollContainerRef,\n  events,\n  days,\n  dayColumnWidth,\n  timeAxisWidth,\n  onEventChange,\n  onEventClick,\n  onResizeNavigate,\n}: UseEventResizeOptions): UseEventResizeReturn {\n  const [resizeState, setResizeState] = useState<EventResizeState | null>(null);\n\n  const resizeRef = useRef<ResizeInfo | null>(null);\n  const onEventChangeRef = useRef(onEventChange);\n  const onEventClickRef = useRef(onEventClick);\n  const onResizeNavigateRef = useRef(onResizeNavigate);\n  const eventsRef = useRef(events);\n  const hourHeightRef = useRef(hourHeight);\n  const daysRef = useRef(days);\n  const dayColumnWidthRef = useRef(dayColumnWidth);\n  const timeAxisWidthRef = useRef(timeAxisWidth);\n\n  useEffect(() => {\n    onEventChangeRef.current = onEventChange;\n  }, [onEventChange]);\n  useEffect(() => {\n    onEventClickRef.current = onEventClick;\n  }, [onEventClick]);\n  useEffect(() => {\n    onResizeNavigateRef.current = onResizeNavigate;\n  }, [onResizeNavigate]);\n  useEffect(() => {\n    eventsRef.current = events;\n  }, [events]);\n  useEffect(() => {\n    hourHeightRef.current = hourHeight;\n  }, [hourHeight]);\n  useEffect(() => {\n    daysRef.current = days;\n  }, [days]);\n  useEffect(() => {\n    dayColumnWidthRef.current = dayColumnWidth;\n  }, [dayColumnWidth]);\n  useEffect(() => {\n    timeAxisWidthRef.current = timeAxisWidth;\n  }, [timeAxisWidth]);\n\n  const autoScrollRAFRef = useRef<number | null>(null);\n  const autoScrollSpeedRef = useRef(0);\n  const edgeNavTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const edgeNavDirectionRef = useRef<number | null>(null);\n\n  const handleMouseMoveRef = useRef<((e: MouseEvent) => void) | null>(null);\n  const handleMouseUpRef = useRef<(() => void) | null>(null);\n\n  const cancelAutoScroll = useCallback(() => {\n    if (autoScrollRAFRef.current !== null) {\n      cancelAnimationFrame(autoScrollRAFRef.current);\n      autoScrollRAFRef.current = null;\n    }\n    autoScrollSpeedRef.current = 0;\n  }, []);\n\n  const cancelEdgeNav = useCallback(() => {\n    if (edgeNavTimerRef.current !== null) {\n      clearTimeout(edgeNavTimerRef.current);\n      edgeNavTimerRef.current = null;\n    }\n    edgeNavDirectionRef.current = null;\n  }, []);\n\n  const scheduleEdgeNav = useCallback(\n    (direction: number) => {\n      if (edgeNavDirectionRef.current === direction) return;\n\n      cancelEdgeNav();\n      edgeNavDirectionRef.current = direction;\n\n      const fireNav = () => {\n        onResizeNavigateRef.current?.(direction);\n        edgeNavTimerRef.current = setTimeout(fireNav, EDGE_NAV_REPEAT_MS);\n      };\n\n      edgeNavTimerRef.current = setTimeout(fireNav, EDGE_NAV_DELAY_MS);\n    },\n    [cancelEdgeNav],\n  );\n\n  const cleanup = useCallback(() => {\n    if (handleMouseMoveRef.current) {\n      window.removeEventListener(\"mousemove\", handleMouseMoveRef.current);\n    }\n    if (handleMouseUpRef.current) {\n      window.removeEventListener(\"mouseup\", handleMouseUpRef.current);\n    }\n    cancelAutoScroll();\n    cancelEdgeNav();\n  }, [cancelAutoScroll, cancelEdgeNav]);\n\n  const startAutoScrollLoop = useCallback(() => {\n    if (autoScrollRAFRef.current !== null) return;\n\n    const tick = () => {\n      const container = scrollContainerRef.current;\n      if (!container) return;\n\n      const speed = autoScrollSpeedRef.current;\n      if (speed === 0) {\n        autoScrollRAFRef.current = null;\n        return;\n      }\n\n      container.scrollTop += speed;\n      autoScrollRAFRef.current = requestAnimationFrame(tick);\n    };\n\n    autoScrollRAFRef.current = requestAnimationFrame(tick);\n  }, [scrollContainerRef]);\n\n  useEffect(() => {\n    handleMouseMoveRef.current = (e: MouseEvent) => {\n      const resize = resizeRef.current;\n      if (!resize) return;\n\n      const deltaY = Math.abs(e.clientY - resize.startClientY);\n      if (!resize.isResizing && deltaY < DRAG_THRESHOLD_PX) return;\n\n      if (!resize.isResizing) {\n        resize.isResizing = true;\n      }\n\n      const container = scrollContainerRef.current;\n      if (!container) return;\n\n      const containerRect = container.getBoundingClientRect();\n      const scrollTop = container.scrollTop;\n      const absoluteY = e.clientY - containerRect.top + scrollTop;\n      const rawMinutes = (absoluteY / hourHeightRef.current) * 60;\n      const snappedMinutes = snapToGrid(rawMinutes);\n\n      let newStartMinutes = resize.originalStartMinutes;\n      let newEndMinutes = resize.originalEndMinutes;\n      let startDate = resize.originalStartDate;\n      let endDate = resize.originalEndDate;\n\n      // Column detection (shared for both edges)\n      const colWidth = dayColumnWidthRef.current;\n      const visibleDays = daysRef.current;\n      const gridLeftEdge = containerRect.left + timeAxisWidthRef.current;\n      const cursorInGrid = e.clientX - gridLeftEdge;\n      const columnIndex = clamp(\n        Math.floor(cursorInGrid / colWidth),\n        0,\n        visibleDays.length - 1,\n      );\n      const targetDay = visibleDays[columnIndex];\n\n      // Unified anchor model: anchor is the opposite end of the grabbed edge\n      const anchorDate =\n        resize.edge === \"bottom\"\n          ? resize.originalStartDate\n          : resize.originalEndDate;\n      const anchorMinutes =\n        resize.edge === \"bottom\"\n          ? resize.originalStartMinutes\n          : resize.originalEndMinutes;\n\n      const cursorTimestamp = targetDay.getTime() + snappedMinutes * 60000;\n      const anchorTimestamp = anchorDate.getTime() + anchorMinutes * 60000;\n\n      let effectiveEdge: \"top\" | \"bottom\";\n\n      if (cursorTimestamp > anchorTimestamp) {\n        // Cursor is after anchor → effective bottom\n        effectiveEdge = \"bottom\";\n        startDate = anchorDate;\n        newStartMinutes = anchorMinutes;\n        endDate = targetDay;\n        newEndMinutes = clamp(snappedMinutes, 0, 1440);\n\n        // Cross-day edge case: cursor at minute 0 on day right after anchor → treat as end-of-anchor-day\n        if (!isSameDay(targetDay, anchorDate)) {\n          const dayAfterAnchor = new Date(anchorDate);\n          dayAfterAnchor.setDate(dayAfterAnchor.getDate() + 1);\n          dayAfterAnchor.setHours(0, 0, 0, 0);\n\n          if (isSameDay(targetDay, dayAfterAnchor) && snappedMinutes === 0) {\n            newEndMinutes = 1440;\n            endDate = anchorDate;\n          }\n        }\n\n        // Same-day: enforce min duration\n        if (isSameDay(startDate, endDate)) {\n          newEndMinutes = clamp(\n            newEndMinutes,\n            anchorMinutes + MIN_DURATION_MINUTES,\n            1440,\n          );\n        }\n      } else if (cursorTimestamp < anchorTimestamp) {\n        // Cursor is before anchor → effective top\n        effectiveEdge = \"top\";\n        endDate = anchorDate;\n        newEndMinutes = anchorMinutes;\n        startDate = targetDay;\n        newStartMinutes = clamp(snappedMinutes, 0, 1440);\n\n        // Cross-day edge case: cursor at minute 1440 on day right before anchor → treat as start-of-anchor-day\n        if (!isSameDay(targetDay, anchorDate)) {\n          const dayBeforeAnchor = new Date(anchorDate);\n          dayBeforeAnchor.setDate(dayBeforeAnchor.getDate() - 1);\n          dayBeforeAnchor.setHours(0, 0, 0, 0);\n\n          if (isSameDay(targetDay, dayBeforeAnchor) && snappedMinutes >= 1440) {\n            newStartMinutes = 0;\n            startDate = anchorDate;\n          }\n        }\n\n        // Same-day: enforce min duration\n        if (isSameDay(startDate, endDate)) {\n          newStartMinutes = clamp(\n            newStartMinutes,\n            0,\n            anchorMinutes - MIN_DURATION_MINUTES,\n          );\n        }\n      } else {\n        // Cursor at anchor → keep minimum duration in original edge direction (no flip)\n        effectiveEdge = resize.edge;\n        if (resize.edge === \"bottom\") {\n          startDate = anchorDate;\n          newStartMinutes = anchorMinutes;\n          endDate = anchorDate;\n          newEndMinutes = anchorMinutes + MIN_DURATION_MINUTES;\n        } else {\n          endDate = anchorDate;\n          newEndMinutes = anchorMinutes;\n          startDate = anchorDate;\n          newStartMinutes = anchorMinutes - MIN_DURATION_MINUTES;\n        }\n      }\n\n      const currentStart = addMinutesToDate(startDate, newStartMinutes);\n      const currentEnd = addMinutesToDate(endDate, newEndMinutes);\n\n      setResizeState({\n        eventId: resize.eventId,\n        event: resize.event,\n        originalStart: resize.event.start,\n        originalEnd: resize.event.end,\n        currentStart,\n        currentEnd,\n        edge: resize.edge,\n        effectiveEdge,\n        isResizing: true,\n        currentEndDate: endDate,\n        currentStartDate: startDate,\n      });\n\n      // Auto-scroll at top/bottom edges\n      const cursorYInContainer = e.clientY - containerRect.top;\n      const containerHeight = containerRect.height;\n\n      if (cursorYInContainer < AUTO_SCROLL_ZONE_PX) {\n        const dist = cursorYInContainer;\n        autoScrollSpeedRef.current =\n          -AUTO_SCROLL_MAX_SPEED * (1 - dist / AUTO_SCROLL_ZONE_PX);\n        startAutoScrollLoop();\n      } else if (cursorYInContainer > containerHeight - AUTO_SCROLL_ZONE_PX) {\n        const dist = containerHeight - cursorYInContainer;\n        autoScrollSpeedRef.current =\n          AUTO_SCROLL_MAX_SPEED * (1 - dist / AUTO_SCROLL_ZONE_PX);\n        startAutoScrollLoop();\n      } else {\n        cancelAutoScroll();\n      }\n\n      // Edge-of-view week navigation\n      const cursorXInGrid = e.clientX - gridLeftEdge;\n      const gridWidth = colWidth * visibleDays.length;\n\n      if (cursorXInGrid < EDGE_ZONE_PX) {\n        scheduleEdgeNav(-7);\n      } else if (cursorXInGrid > gridWidth - EDGE_ZONE_PX) {\n        scheduleEdgeNav(7);\n      } else {\n        cancelEdgeNav();\n      }\n    };\n\n    handleMouseUpRef.current = () => {\n      const resize = resizeRef.current;\n      if (!resize) return;\n\n      cleanup();\n\n      if (resize.isResizing) {\n        setResizeState((prev) => {\n          if (!prev) return null;\n\n          const event = eventsRef.current.find((e) => e.id === resize.eventId);\n          if (!event) return null;\n\n          const MS_IN_24H = 24 * 60 * 60 * 1000;\n          const isLongerThan24h =\n            prev.currentEnd.getTime() - prev.currentStart.getTime() > MS_IN_24H;\n\n          onEventChangeRef.current?.({\n            ...event,\n            start: prev.currentStart,\n            end: prev.currentEnd,\n            isAllDay: isLongerThan24h,\n          });\n\n          return null;\n        });\n      } else {\n        setResizeState(null);\n      }\n\n      resizeRef.current = null;\n    };\n  }, [\n    scrollContainerRef,\n    cleanup,\n    cancelAutoScroll,\n    startAutoScrollLoop,\n    scheduleEdgeNav,\n    cancelEdgeNav,\n  ]);\n\n  const handleResizeMouseDown = useCallback(\n    (e: React.MouseEvent, event: CalendarEvent, edge: \"top\" | \"bottom\") => {\n      if (e.button !== 0) return;\n\n      e.stopPropagation();\n\n      onEventClickRef.current?.(event);\n\n      const originalStartMinutes =\n        event.start.getHours() * 60 + event.start.getMinutes();\n      const originalEndMinutes =\n        event.end.getHours() * 60 + event.end.getMinutes();\n\n      const originalStartDate = startOfDay(event.start);\n      const originalEndDate = startOfDay(event.end);\n\n      resizeRef.current = {\n        eventId: event.id,\n        event,\n        edge,\n        startClientY: e.clientY,\n        isResizing: false,\n        originalStartMinutes,\n        originalEndMinutes,\n        originalStartDate,\n        originalEndDate,\n      };\n\n      setResizeState({\n        eventId: event.id,\n        event,\n        originalStart: event.start,\n        originalEnd: event.end,\n        currentStart: event.start,\n        currentEnd: event.end,\n        edge,\n        effectiveEdge: edge,\n        isResizing: false,\n        currentEndDate: originalEndDate,\n        currentStartDate: originalStartDate,\n      });\n\n      if (handleMouseMoveRef.current) {\n        window.addEventListener(\"mousemove\", handleMouseMoveRef.current);\n      }\n      if (handleMouseUpRef.current) {\n        window.addEventListener(\"mouseup\", handleMouseUpRef.current);\n      }\n    },\n    [],\n  );\n\n  useEffect(() => {\n    return cleanup;\n  }, [cleanup]);\n\n  return { resizeState, handleResizeMouseDown };\n}\n",
      "type": "registry:hook",
      "target": "hooks/layouts/use-event-resize.ts"
    },
    {
      "path": "hooks/layouts/use-horizontal-scroll.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\ninterface UseHorizontalScrollOptions {\n  containerRef: React.RefObject<HTMLDivElement | null>;\n  dayColumnWidth: number;\n  onNavigate: (daysDelta: number) => void;\n  disabled?: boolean;\n}\n\ninterface UseHorizontalScrollReturn {\n  scrollOffset: number;\n  slideOffset: number;\n  isScrolling: boolean;\n  isAnimating: boolean;\n  triggerSlideAnimation: (daysDelta: number) => void;\n}\n\nconst SCROLL_END_DEBOUNCE_MS = 150;\nconst SNAP_ANIMATION_MS = 200;\n\nexport function useHorizontalScroll({\n  containerRef,\n  dayColumnWidth,\n  onNavigate,\n  disabled,\n}: UseHorizontalScrollOptions): UseHorizontalScrollReturn {\n  const [scrollOffset, setScrollOffset] = useState(0);\n  const [slideOffset, setSlideOffset] = useState(0);\n  const [isScrolling, setIsScrolling] = useState(false);\n  const [isAnimating, setIsAnimating] = useState(false);\n\n  const scrollEndTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const accumulatedDelta = useRef(0);\n  const onNavigateRef = useRef(onNavigate);\n  useEffect(() => {\n    onNavigateRef.current = onNavigate;\n  }, [onNavigate]);\n\n  const snapAndNavigate = useCallback(\n    (offset: number) => {\n      if (dayColumnWidth <= 0) return;\n\n      const daysDelta = Math.round(offset / dayColumnWidth);\n\n      // If scroll distance < half a day-column width, snap back\n      if (daysDelta === 0) {\n        setIsAnimating(true);\n        setScrollOffset(0);\n        setTimeout(() => {\n          setIsAnimating(false);\n          setIsScrolling(false);\n        }, SNAP_ANIMATION_MS);\n        return;\n      }\n\n      // Snap to exact day boundary then navigate\n      const targetOffset = daysDelta * dayColumnWidth;\n      setIsAnimating(true);\n      setScrollOffset(targetOffset);\n\n      setTimeout(() => {\n        onNavigateRef.current(-daysDelta);\n        setScrollOffset(0);\n        setIsAnimating(false);\n        setIsScrolling(false);\n        accumulatedDelta.current = 0;\n      }, SNAP_ANIMATION_MS);\n    },\n    [dayColumnWidth],\n  );\n\n  // Programmatic slide animation for button/keyboard navigation\n  // Uses slideOffset (not scrollOffset) so dynamicBuffer isn't affected\n  // Does NOT call onNavigate — caller is responsible for having already changed the date\n  const triggerSlideAnimation = useCallback(\n    (daysDelta: number) => {\n      if (dayColumnWidth <= 0 || isAnimating || isScrolling) return;\n\n      // Start from the opposite direction to create slide effect\n      const startOffset = daysDelta * dayColumnWidth;\n      setSlideOffset(startOffset);\n\n      // Force a reflow then animate to 0\n      requestAnimationFrame(() => {\n        requestAnimationFrame(() => {\n          setIsAnimating(true);\n          setSlideOffset(0);\n          setTimeout(() => {\n            setIsAnimating(false);\n          }, SNAP_ANIMATION_MS);\n        });\n      });\n    },\n    [dayColumnWidth, isAnimating, isScrolling],\n  );\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const handleWheel = (e: WheelEvent) => {\n      if (disabled) return;\n\n      // Only handle horizontal-dominant scrolls\n      if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;\n\n      e.preventDefault();\n\n      setIsScrolling(true);\n\n      // Negate deltaX: scroll right (positive deltaX) = move calendar left = navigate forward\n      accumulatedDelta.current += -e.deltaX;\n      setScrollOffset(accumulatedDelta.current);\n\n      // Reset debounce timer\n      if (scrollEndTimer.current) {\n        clearTimeout(scrollEndTimer.current);\n      }\n\n      scrollEndTimer.current = setTimeout(() => {\n        snapAndNavigate(accumulatedDelta.current);\n        accumulatedDelta.current = 0;\n      }, SCROLL_END_DEBOUNCE_MS);\n    };\n\n    container.addEventListener(\"wheel\", handleWheel, { passive: false });\n\n    return () => {\n      container.removeEventListener(\"wheel\", handleWheel);\n      if (scrollEndTimer.current) {\n        clearTimeout(scrollEndTimer.current);\n      }\n    };\n  }, [containerRef, snapAndNavigate, disabled]);\n\n  return {\n    scrollOffset,\n    slideOffset,\n    isScrolling,\n    isAnimating,\n    triggerSlideAnimation,\n  };\n}\n",
      "type": "registry:hook",
      "target": "hooks/layouts/use-horizontal-scroll.ts"
    },
    {
      "path": "hooks/layouts/use-mobile.ts",
      "content": "import * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined,\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener(\"change\", onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener(\"change\", onChange);\n  }, []);\n\n  return !!isMobile;\n}\n",
      "type": "registry:hook",
      "target": "hooks/layouts/use-mobile.ts"
    },
    {
      "path": "lib/layouts/event-utils.ts",
      "content": "import {\n  isSameDay,\n  startOfDay,\n  addDays,\n  differenceInMinutes,\n  isWithinInterval,\n  areIntervalsOverlapping,\n} from \"date-fns\";\nimport type {\n  CalendarEvent,\n  PositionedEvent,\n  WeekDay,\n} from \"@/components/layouts/calendar/week-view-types\";\n\n/** Returns true when a timed event spans across midnight into a different day. */\nexport function isMultiDayEvent(event: CalendarEvent): boolean {\n  if (event.isAllDay) {\n    return false;\n  }\n  return !isSameDay(event.start, event.end);\n}\n\n/**\n * Filters events for a specific day (excluding all-day and multi-day events)\n */\nexport function getEventsForDay(\n  events: CalendarEvent[],\n  day: WeekDay,\n): CalendarEvent[] {\n  const dayStart = startOfDay(day.date);\n  const dayEnd = addDays(dayStart, 1);\n\n  return events.filter((event) => {\n    if (event.isAllDay || isMultiDayEvent(event)) {\n      return false;\n    }\n    // Event spans this day if it starts before day end AND ends after day start\n    return event.start < dayEnd && event.end > dayStart;\n  });\n}\n\n/**\n * Gets all-day events that span a specific day\n */\nexport function getAllDayEventsForDay(\n  events: CalendarEvent[],\n  day: WeekDay,\n): CalendarEvent[] {\n  return events.filter((event) => {\n    if (!event.isAllDay) {\n      return false;\n    }\n    const dayStart = startOfDay(day.date);\n    const dayEnd = new Date(dayStart);\n    dayEnd.setHours(23, 59, 59, 999);\n\n    return (\n      isWithinInterval(dayStart, { start: event.start, end: event.end }) ||\n      isSameDay(event.start, day.date) ||\n      isSameDay(event.end, day.date)\n    );\n  });\n}\n\n/**\n * Checks if two events overlap in time\n */\nfunction eventsOverlap(a: CalendarEvent, b: CalendarEvent): boolean {\n  return areIntervalsOverlapping(\n    { start: a.start, end: a.end },\n    { start: b.start, end: b.end },\n  );\n}\n\n/**\n * Groups overlapping events into columns\n */\nfunction assignColumns(\n  events: CalendarEvent[],\n): Map<string, { column: number; totalColumns: number }> {\n  const columnAssignments = new Map<\n    string,\n    { column: number; totalColumns: number }\n  >();\n\n  if (events.length === 0) {\n    return columnAssignments;\n  }\n\n  // Sort events by start time, then by duration (longer first)\n  const sortedEvents = [...events].sort((a, b) => {\n    const startDiff = a.start.getTime() - b.start.getTime();\n    if (startDiff !== 0) {\n      return startDiff;\n    }\n    // Longer events first\n    const durationA = a.end.getTime() - a.start.getTime();\n    const durationB = b.end.getTime() - b.start.getTime();\n    return durationB - durationA;\n  });\n\n  // Find groups of overlapping events\n  const groups: CalendarEvent[][] = [];\n  const processed = new Set<string>();\n\n  for (const event of sortedEvents) {\n    if (processed.has(event.id)) {\n      continue;\n    }\n\n    const group: CalendarEvent[] = [event];\n    processed.add(event.id);\n\n    // Find all events that overlap with any event in the group\n    let foundNew = true;\n    while (foundNew) {\n      foundNew = false;\n      for (const otherEvent of sortedEvents) {\n        if (processed.has(otherEvent.id)) {\n          continue;\n        }\n        const overlapsWithGroup = group.some((groupEvent) =>\n          eventsOverlap(groupEvent, otherEvent),\n        );\n        if (!overlapsWithGroup) {\n          continue;\n        }\n        group.push(otherEvent);\n        processed.add(otherEvent.id);\n        foundNew = true;\n      }\n    }\n\n    groups.push(group);\n  }\n\n  // Assign columns within each group\n  for (const group of groups) {\n    const columns: CalendarEvent[][] = [];\n\n    // Sort group by start time\n    group.sort((a, b) => a.start.getTime() - b.start.getTime());\n\n    for (const event of group) {\n      // Find the first column where this event doesn't overlap with existing events\n      let placed = false;\n      for (let colIndex = 0; colIndex < columns.length; colIndex++) {\n        const column = columns[colIndex];\n        const overlapsWithColumn = column.some((colEvent) =>\n          eventsOverlap(colEvent, event),\n        );\n        if (overlapsWithColumn) {\n          continue;\n        }\n        column.push(event);\n        columnAssignments.set(event.id, { column: colIndex, totalColumns: 0 });\n        placed = true;\n        break;\n      }\n\n      if (placed) {\n        continue;\n      }\n\n      // Create new column\n      columns.push([event]);\n      columnAssignments.set(event.id, {\n        column: columns.length - 1,\n        totalColumns: 0,\n      });\n    }\n\n    // Update total columns for all events in group\n    const totalColumns = columns.length;\n    for (const event of group) {\n      const assignment = columnAssignments.get(event.id);\n      if (!assignment) {\n        continue;\n      }\n      assignment.totalColumns = totalColumns;\n    }\n  }\n\n  return columnAssignments;\n}\n\n/**\n * Calculates positioned events for rendering in the grid.\n * @param rightGapPercent - right gap in percentage. Defaults to 8.\n *   Pass 0 for day view so events fill the full column width.\n */\nexport function calculatePositionedEvents(\n  events: CalendarEvent[],\n  day: WeekDay,\n  rightGapPercent = 8,\n): PositionedEvent[] {\n  const dayEvents = getEventsForDay(events, day);\n\n  if (dayEvents.length === 0) {\n    return [];\n  }\n\n  const columnAssignments = assignColumns(dayEvents);\n  const dayStart = startOfDay(day.date);\n\n  const nextDayMidnight = addDays(dayStart, 1);\n\n  return dayEvents.map((event) => {\n    const assignment = columnAssignments.get(event.id) ?? {\n      column: 0,\n      totalColumns: 1,\n    };\n\n    // Compute effective start/end clamped to this day's boundaries\n    const effectiveStart = event.start > dayStart ? event.start : dayStart;\n    const effectiveEnd =\n      event.end < nextDayMidnight ? event.end : nextDayMidnight;\n\n    // Calculate top position (percentage from day start)\n    const minutesFromDayStart = differenceInMinutes(effectiveStart, dayStart);\n    const top = (minutesFromDayStart / (24 * 60)) * 100;\n\n    // Calculate height (percentage of the day)\n    const durationMinutes = differenceInMinutes(effectiveEnd, effectiveStart);\n    const height = (durationMinutes / (24 * 60)) * 100;\n\n    // Determine segment position for multi-day events\n    const startsOnDay = isSameDay(event.start, dayStart);\n    const endsOnDay = event.end <= nextDayMidnight;\n    let segmentPosition: \"start\" | \"middle\" | \"end\" | \"full\" = \"full\";\n    if (startsOnDay && !endsOnDay) {\n      segmentPosition = \"start\";\n    } else if (!startsOnDay && endsOnDay) {\n      segmentPosition = \"end\";\n    } else if (!startsOnDay && !endsOnDay) {\n      segmentPosition = \"middle\";\n    }\n\n    // Calculate left and width based on column assignment\n    // Events cascade with overlap - leftmost event has no gap, rightmost has gap\n    const rightGap = rightGapPercent;\n    const overlapAmount = 8; // percentage overlap between adjacent events\n    const { column, totalColumns } = assignment;\n\n    let left: number;\n    let width: number;\n\n    if (totalColumns === 1) {\n      // Single event: full width with right gap\n      left = 0;\n      width = 100 - rightGap;\n    } else {\n      // Multiple overlapping events\n      // Formula: n * eventWidth - (n-1) * overlap = availableWidth\n      // So: eventWidth = (availableWidth + (n-1) * overlap) / n\n      const eventWidth =\n        (100 - rightGap + overlapAmount * (totalColumns - 1)) / totalColumns;\n\n      left = column * (eventWidth - overlapAmount);\n\n      if (column === totalColumns - 1) {\n        // Last event: fill to end (has the right gap)\n        width = 100 - rightGap - left;\n      } else {\n        // Other events extend under the next one (no gap)\n        width = eventWidth;\n      }\n    }\n\n    return {\n      event,\n      top,\n      height,\n      left,\n      width,\n      column: assignment.column,\n      totalColumns: assignment.totalColumns,\n      segmentPosition,\n    };\n  });\n}\n\n/**\n * Groups all-day events by their visual row (for stacking)\n */\nexport interface AllDayEventRow {\n  event: CalendarEvent;\n  startColumn: number;\n  endColumn: number;\n  row: number;\n}\n\nexport function calculateAllDayEventRows(\n  events: CalendarEvent[],\n  days: WeekDay[],\n): AllDayEventRow[] {\n  const allDayEvents = events.filter((e) => e.isAllDay || isMultiDayEvent(e));\n\n  if (allDayEvents.length === 0) {\n    return [];\n  }\n\n  // Sort by start date, then by duration (longer first)\n  const sortedEvents = [...allDayEvents].sort((a, b) => {\n    const startDiff = a.start.getTime() - b.start.getTime();\n    if (startDiff !== 0) {\n      return startDiff;\n    }\n    const durationA = a.end.getTime() - a.start.getTime();\n    const durationB = b.end.getTime() - b.start.getTime();\n    return durationB - durationA;\n  });\n\n  const rows: AllDayEventRow[] = [];\n  const occupiedRows: Map<number, { start: number; end: number }[]> = new Map();\n\n  for (const event of sortedEvents) {\n    // Find start and end columns\n    let startColumn = -1;\n    let endColumn = -1;\n\n    for (let i = 0; i < days.length; i++) {\n      const day = days[i];\n      const dayStart = startOfDay(day.date);\n\n      if (\n        isSameDay(event.start, day.date) ||\n        (event.start <= dayStart && event.end >= dayStart)\n      ) {\n        if (startColumn === -1) {\n          startColumn = i;\n        }\n        endColumn = i;\n      }\n    }\n\n    if (startColumn === -1) {\n      continue;\n    }\n\n    // Find the first row where this event fits\n    let targetRow = 0;\n    let foundRow = false;\n\n    while (!foundRow) {\n      const rowOccupied = occupiedRows.get(targetRow) ?? [];\n      const hasConflict = rowOccupied.some(\n        (occupied) =>\n          !(endColumn < occupied.start || startColumn > occupied.end),\n      );\n\n      if (!hasConflict) {\n        foundRow = true;\n        break;\n      }\n\n      targetRow++;\n    }\n\n    // Mark the row as occupied\n    const rowOccupied = occupiedRows.get(targetRow) ?? [];\n    rowOccupied.push({ start: startColumn, end: endColumn });\n    occupiedRows.set(targetRow, rowOccupied);\n\n    rows.push({\n      event,\n      startColumn,\n      endColumn,\n      row: targetRow,\n    });\n  }\n\n  return rows;\n}\n",
      "type": "registry:lib",
      "target": "lib/layouts/event-utils.ts"
    },
    {
      "path": "lib/layouts/mock-events.ts",
      "content": "import type {\n  CalendarEvent,\n  EventColor,\n  EventReminder,\n} from \"@/components/layouts/calendar/week-view-types\";\n\nfunction d(month: number, day: number, hour: number, minute = 0): Date {\n  return new Date(2026, month - 1, day, hour, minute);\n}\n\nfunction dy(\n  year: number,\n  month: number,\n  day: number,\n  hour: number,\n  minute = 0,\n): Date {\n  return new Date(year, month - 1, day, hour, minute);\n}\n\ninterface EventOpts {\n  isAllDay?: boolean;\n  description?: string;\n  location?: string;\n  timezone?: string;\n  recurrence?: string;\n  reminders?: EventReminder[];\n  status?: \"busy\" | \"free\";\n  visibility?: \"default\" | \"public\" | \"private\";\n  calendarEmail?: string;\n}\n\nfunction ev(\n  id: string,\n  title: string,\n  start: Date,\n  end: Date,\n  color: EventColor,\n  calendarId: string,\n  opts?: EventOpts,\n): CalendarEvent {\n  return { id, title, start, end, color, calendarId, ...opts };\n}\n\n/**\n * Generates mock events with fixed dates across January–March 2026.\n *\n * Calendar mapping:\n *   you@example.com       → red     (main email)\n *   Work               → blue    (work projects)\n *   Personal           → purple  (personal)\n *   Family             → orange  (family)\n *   Side Projects      → yellow  (side projects)\n *   Fitness            → green   (gym/sports)\n *   Holidays in Brazil → green   (subscribed)\n */\nexport function generateMockEvents(): CalendarEvent[] {\n  return [\n    // ── January 2026 ──\n\n    // Week of Jan 4 (Sun Jan 4 – Sat Jan 10)\n    ev(\n      \"j01\",\n      \"New Year Planning\",\n      d(1, 5, 9),\n      d(1, 5, 10, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description:\n          \"Align on Q1 goals and key deliverables for the engineering team\",\n        reminders: [{ amount: 1, unit: \"hours\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"j02\",\n      \"Team Standup\",\n      d(1, 5, 10, 30),\n      d(1, 5, 11),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"j03\",\n      \"Lunch with Alex\",\n      d(1, 6, 12),\n      d(1, 6, 13),\n      \"purple\",\n      \"Personal\",\n      { location: \"Ichiran Ramen\", calendarEmail: \"you@example.com\" },\n    ),\n    ev(\n      \"j04\",\n      \"Client Onboarding\",\n      d(1, 7, 14),\n      d(1, 7, 15, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description:\n          \"Walk through platform setup and API integration with Acme Corp\",\n        reminders: [\n          { amount: 15, unit: \"minutes\" },\n          { amount: 1, unit: \"hours\" },\n        ],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"j04b\",\n      \"1:1 with Manager\",\n      d(1, 7, 15, 30),\n      d(1, 7, 16),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"First 1:1 of the year — set annual goals\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"j05\", \"Gym\", d(1, 8, 18), d(1, 8, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j06\", \"Friday Wrap-up\", d(1, 9, 16), d(1, 9, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j06b\", \"Happy Hour\", d(1, 9, 17), d(1, 9, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      location: \"The Draft House\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n\n    // Week of Jan 11 (Sun Jan 11 – Sat Jan 17)\n    ev(\n      \"j07\",\n      \"Team Standup\",\n      d(1, 12, 9),\n      d(1, 12, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"j08\",\n      \"Product Roadmap Review\",\n      d(1, 12, 10),\n      d(1, 12, 11, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description:\n          \"Review H1 roadmap with product and design leads. Bring updated estimates.\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"j09\",\n      \"Design Sync\",\n      d(1, 13, 11),\n      d(1, 13, 12),\n      \"yellow\",\n      \"Side Projects\",\n      { calendarEmail: \"you@example.com\" },\n    ),\n    ev(\n      \"j10\",\n      \"1:1 with Manager\",\n      d(1, 14, 15),\n      d(1, 14, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Career growth discussion, Q1 goals check-in\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"j10b\", \"Gym\", d(1, 15, 18), d(1, 15, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j11\", \"Dentist\", d(1, 15, 10), d(1, 15, 11), \"purple\", \"Personal\", {\n      description: \"Regular cleaning + check-up. Bring insurance card.\",\n      location: \"SmileCare Dental\",\n      reminders: [\n        { amount: 1, unit: \"hours\" },\n        { amount: 1, unit: \"days\" },\n      ],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"j12\",\n      \"Movie Night\",\n      d(1, 16, 19),\n      d(1, 16, 21, 30),\n      \"orange\",\n      \"Family\",\n      {\n        description:\n          \"Watching the new sci-fi movie everyone's been talking about\",\n        location: \"AMC Theater\",\n        reminders: [{ amount: 30, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"j12b\", \"Friday Wrap-up\", d(1, 16, 16), d(1, 16, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j12c\", \"Happy Hour\", d(1, 16, 17), d(1, 16, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      location: \"The Draft House\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"j13\",\n      \"MLK Day\",\n      d(1, 19, 0),\n      d(1, 19, 0),\n      \"green\",\n      \"Holidays in Brazil\",\n      { isAllDay: true, calendarEmail: \"you@example.com\" },\n    ),\n\n    // Week of Jan 18 (Sun Jan 18 – Sat Jan 24)\n    ev(\n      \"j14\",\n      \"Team Standup\",\n      d(1, 19, 9),\n      d(1, 19, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"j15\",\n      \"Sprint Planning\",\n      d(1, 19, 10),\n      d(1, 19, 11, 30),\n      \"blue\",\n      \"Work\",\n      {\n        recurrence: \"Every 2 weeks on Mon\",\n        description: \"Scope sprint 3 stories, assign points and owners\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"j16\",\n      \"Coffee Chat\",\n      d(1, 20, 14),\n      d(1, 20, 14, 30),\n      \"purple\",\n      \"Personal\",\n      { location: \"Blue Bottle Coffee\", calendarEmail: \"you@example.com\" },\n    ),\n    ev(\n      \"j17\",\n      \"Architecture Review\",\n      d(1, 21, 13),\n      d(1, 21, 14, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description:\n          \"Review proposed microservice migration plan. Discuss trade-offs with team.\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"j17b\",\n      \"1:1 with Manager\",\n      d(1, 21, 15),\n      d(1, 21, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Weekly sync — project assignments and growth plan\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"j18\", \"Gym\", d(1, 22, 18), d(1, 22, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j18b\", \"Friday Wrap-up\", d(1, 23, 16), d(1, 23, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j19\", \"Happy Hour\", d(1, 23, 17), d(1, 23, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Drinks with the team at the usual spot\",\n      location: \"The Draft House\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j20\", \"Brunch\", d(1, 24, 11), d(1, 24, 13), \"orange\", \"Family\", {\n      description: \"Monthly family brunch — Mom's picking the place\",\n      location: \"Café Lola\",\n      reminders: [{ amount: 1, unit: \"hours\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n\n    // Week of Jan 25 (Sun Jan 25 – Sat Jan 31)\n    ev(\n      \"j21\",\n      \"Team Standup\",\n      d(1, 26, 9),\n      d(1, 26, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"j22\",\n      \"Quarterly Review Prep\",\n      d(1, 26, 11),\n      d(1, 26, 12, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description: \"Prepare slides and metrics for Q4 review presentation\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"j23\",\n      \"Client Demo\",\n      d(1, 27, 14),\n      d(1, 27, 15),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Demo new dashboard features to Acme Corp stakeholders\",\n        reminders: [\n          { amount: 30, unit: \"minutes\" },\n          { amount: 1, unit: \"hours\" },\n        ],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"j24\",\n      \"Open Source Contrib\",\n      d(1, 28, 13),\n      d(1, 28, 14),\n      \"yellow\",\n      \"Side Projects\",\n      { calendarEmail: \"you@example.com\" },\n    ),\n    ev(\n      \"j24b\",\n      \"1:1 with Manager\",\n      d(1, 28, 15),\n      d(1, 28, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Quarterly review prep discussion\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"j25\", \"Retro\", d(1, 29, 14), d(1, 29, 15), \"blue\", \"Work\", {\n      recurrence: \"Every 2 weeks on Thu\",\n      description: \"Sprint 2 retrospective — what went well, what to improve\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j26\", \"Gym\", d(1, 29, 18), d(1, 29, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      description: \"Full body circuit training\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j26b\", \"Friday Wrap-up\", d(1, 30, 16), d(1, 30, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"j26c\", \"Happy Hour\", d(1, 30, 17), d(1, 30, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      location: \"The Draft House\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"j27\",\n      \"Month-End Report\",\n      d(1, 30, 10),\n      d(1, 30, 11, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Compile January metrics and submit to finance\",\n        reminders: [{ amount: 1, unit: \"hours\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n\n    // ── February 2026 ──\n\n    // Week of Feb 1 (Sun Feb 1 – Sat Feb 7)\n    ev(\n      \"f01\",\n      \"Team Standup\",\n      d(2, 2, 9),\n      d(2, 2, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\"f02\", \"Q1 Kickoff\", d(2, 2, 10), d(2, 2, 12), \"blue\", \"Work\", {\n      description: \"Company-wide Q1 kickoff. CEO presenting vision and OKRs.\",\n      location: \"Conference Room A\",\n      reminders: [\n        { amount: 30, unit: \"minutes\" },\n        { amount: 1, unit: \"days\" },\n      ],\n      calendarEmail: \"you@example.com\",\n      status: \"busy\",\n      timezone: \"GMT-3 Sao Paulo\",\n    }),\n    ev(\n      \"f03\",\n      \"Lunch with Sarah\",\n      d(2, 3, 12),\n      d(2, 3, 13),\n      \"purple\",\n      \"Personal\",\n      {\n        description: \"She wants to chat about switching jobs — bring advice\",\n        location: \"Sweetgreen\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f03b\",\n      \"1:1 with Manager\",\n      d(2, 4, 15),\n      d(2, 4, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f04\",\n      \"Design Review\",\n      d(2, 4, 14),\n      d(2, 4, 15, 30),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        description:\n          \"Review component library updates — new button variants and color tokens\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"f05\", \"Workshop\", d(2, 5, 9), d(2, 5, 12), \"blue\", \"Work\", {\n      description:\n        \"React Patterns Workshop — advanced hooks, composition, and performance\",\n      location: \"Main Hall\",\n      reminders: [{ amount: 1, unit: \"hours\" }],\n      calendarEmail: \"you@example.com\",\n      status: \"busy\",\n      visibility: \"public\",\n    }),\n    ev(\"f06\", \"Gym\", d(2, 5, 18), d(2, 5, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"f06b\", \"Friday Wrap-up\", d(2, 6, 16), d(2, 6, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"f06c\", \"Happy Hour\", d(2, 6, 17), d(2, 6, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      location: \"The Brewery\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"f07\",\n      \"Family Dinner\",\n      d(2, 6, 12),\n      d(2, 6, 13, 30),\n      \"orange\",\n      \"Family\",\n      {\n        description: \"Dad's birthday celebration dinner\",\n        location: \"Olive Garden\",\n        reminders: [\n          { amount: 1, unit: \"hours\" },\n          { amount: 1, unit: \"days\" },\n        ],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n\n    // Week of Feb 8 (Sun Feb 8 – Sat Feb 14)\n    ev(\n      \"f08\",\n      \"Team Standup\",\n      d(2, 9, 9),\n      d(2, 9, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"f09\",\n      \"Project Planning\",\n      d(2, 9, 10),\n      d(2, 9, 11, 30),\n      \"blue\",\n      \"Work\",\n      {\n        recurrence: \"Every 2 weeks on Mon\",\n        description:\n          \"Scope new auth service project — timeline, resources, dependencies\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f09b\",\n      \"Budget Review\",\n      d(2, 9, 10, 30),\n      d(2, 9, 11),\n      \"red\",\n      \"you@example.com\",\n      {\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f10\",\n      \"Client Call\",\n      d(2, 10, 14, 30),\n      d(2, 10, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        description:\n          \"Monthly sync with Acme Corp — review integration progress and blockers\",\n        reminders: [\n          { amount: 10, unit: \"minutes\" },\n          { amount: 1, unit: \"hours\" },\n        ],\n        calendarEmail: \"you@example.com\",\n        timezone: \"GMT-3 Sao Paulo\",\n        status: \"busy\",\n      },\n    ),\n    ev(\"f10b\", \"Infra Sync\", d(2, 10, 14), d(2, 10, 15), \"blue\", \"Work\", {\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"f11\",\n      \"1:1 with Manager\",\n      d(2, 11, 15),\n      d(2, 11, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Mid-quarter check-in, discuss promotion timeline\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f11b\",\n      \"Security Review\",\n      d(2, 11, 14),\n      d(2, 11, 15, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description: \"Review auth flow security audit findings\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"f12\", \"Sprint Review\", d(2, 12, 10), d(2, 12, 11), \"blue\", \"Work\", {\n      recurrence: \"Every 2 weeks on Thu\",\n      description: \"Demo sprint 3 deliverables to stakeholders\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"f12b\",\n      \"Perf Monitoring Setup\",\n      d(2, 12, 10, 30),\n      d(2, 12, 11, 30),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        description: \"Set up Lighthouse CI for the component library\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"f12c\", \"Gym\", d(2, 12, 18), d(2, 12, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"f12d\", \"Friday Wrap-up\", d(2, 13, 16), d(2, 13, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"f12e\", \"Happy Hour\", d(2, 13, 17), d(2, 13, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      location: \"Wine Bar\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"f13\",\n      \"Valentine's Dinner\",\n      d(2, 14, 19),\n      d(2, 14, 21),\n      \"purple\",\n      \"Personal\",\n      {\n        description:\n          \"Reservation at that Italian place — don't forget flowers!\",\n        location: \"Trattoria Roma\",\n        reminders: [\n          { amount: 2, unit: \"hours\" },\n          { amount: 1, unit: \"days\" },\n        ],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f14\",\n      \"Valentine's Day\",\n      d(2, 14, 0),\n      d(2, 14, 0),\n      \"green\",\n      \"Holidays in Brazil\",\n      { isAllDay: true, calendarEmail: \"you@example.com\" },\n    ),\n\n    // Week of Feb 15 (Sun Feb 15 – Sat Feb 21)\n    ev(\n      \"f15\",\n      \"Team Standup\",\n      d(2, 16, 9),\n      d(2, 16, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\"f16\", \"Roadmap Sync\", d(2, 16, 11), d(2, 16, 12), \"blue\", \"Work\", {\n      description:\n        \"Align engineering and product on H1 priorities and delivery dates\",\n      reminders: [{ amount: 15, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"f17\",\n      \"Lunch with Alex\",\n      d(2, 17, 12),\n      d(2, 17, 13),\n      \"purple\",\n      \"Personal\",\n      {\n        description:\n          \"He's launching his startup next month — wants feedback on the pitch\",\n        location: \"Chipotle\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f18\",\n      \"Architecture Deep Dive\",\n      d(2, 18, 13),\n      d(2, 18, 15),\n      \"blue\",\n      \"Work\",\n      {\n        description:\n          \"Deep dive into event-driven architecture for the notifications service\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"f19\", \"Gym\", d(2, 19, 18), d(2, 19, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      description: \"HIIT class + core work\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"f19b\", \"Friday Wrap-up\", d(2, 20, 16), d(2, 20, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"f20\", \"Happy Hour\", d(2, 20, 17), d(2, 20, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Celebrating Jake's promotion\",\n      location: \"Rooftop Bar\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"f21\",\n      \"Presidents' Day\",\n      d(2, 16, 0),\n      d(2, 16, 0),\n      \"green\",\n      \"Holidays in Brazil\",\n      { isAllDay: true, calendarEmail: \"you@example.com\" },\n    ),\n    ev(\"f22\", \"Brunch\", d(2, 21, 11), d(2, 21, 13), \"orange\", \"Family\", {\n      description: \"Sister's visiting from out of town\",\n      location: \"The Breakfast Club\",\n      reminders: [{ amount: 1, unit: \"hours\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"f23\",\n      \"Blog Post Draft\",\n      d(2, 18, 10),\n      d(2, 18, 11),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n\n    // Week of Feb 22 (Sun Feb 22 – Sat Feb 28) — busy week with overlaps\n    ev(\n      \"f24\",\n      \"Team Standup\",\n      d(2, 23, 9),\n      d(2, 23, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"f25\",\n      \"Sprint Planning\",\n      d(2, 23, 10),\n      d(2, 23, 11, 30),\n      \"blue\",\n      \"Work\",\n      {\n        recurrence: \"Every 2 weeks on Mon\",\n        description: \"Plan sprint 4 — finalize scope and capacity\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f25b\",\n      \"Investor Update Call\",\n      d(2, 23, 10, 30),\n      d(2, 23, 11),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Quick sync with CFO before investor email goes out\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"f26\",\n      \"UX Research Debrief\",\n      d(2, 24, 14),\n      d(2, 24, 15),\n      \"blue\",\n      \"Work\",\n      {\n        description:\n          \"Go over user interview findings from last week's research sessions\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f26b\",\n      \"Design Critique\",\n      d(2, 24, 14, 30),\n      d(2, 24, 15, 30),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        description: \"Review new onboarding flow wireframes with design team\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f26c\",\n      \"Candidate Interview\",\n      d(2, 24, 15),\n      d(2, 24, 16),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Senior frontend engineer — system design round\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"f27\",\n      \"Coffee Chat\",\n      d(2, 25, 9, 30),\n      d(2, 25, 10),\n      \"purple\",\n      \"Personal\",\n      { location: \"Starbucks Reserve\", calendarEmail: \"you@example.com\" },\n    ),\n    ev(\"f27b\", \"Platform Sync\", d(2, 25, 9), d(2, 25, 10, 30), \"blue\", \"Work\", {\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"f27c\", \"API Review\", d(2, 25, 10), d(2, 25, 11), \"blue\", \"Work\", {\n      description: \"Review REST→GraphQL migration proposal\",\n      reminders: [{ amount: 5, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"f27d\",\n      \"Hiring Debrief\",\n      d(2, 25, 14),\n      d(2, 25, 15),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Debrief on yesterday's candidate — collect scorecards\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f27e\",\n      \"1:1 with Manager\",\n      d(2, 25, 15),\n      d(2, 25, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Weekly sync — promotion timeline update\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f28\",\n      \"Demo Day\",\n      d(2, 26, 14),\n      d(2, 26, 16),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Present Q1 progress to leadership — bring laptop charger\",\n        location: \"Auditorium\",\n        reminders: [\n          { amount: 1, unit: \"hours\" },\n          { amount: 1, unit: \"days\" },\n        ],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n        visibility: \"public\",\n      },\n    ),\n    ev(\n      \"f28b\",\n      \"Stakeholder Check-in\",\n      d(2, 26, 14, 30),\n      d(2, 26, 15, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description: \"Quick sync with product on demo feedback\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f28c\",\n      \"Side Project Standup\",\n      d(2, 26, 15),\n      d(2, 26, 15, 30),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"f29\", \"Retro\", d(2, 27, 14), d(2, 27, 15), \"blue\", \"Work\", {\n      recurrence: \"Every 2 weeks on Fri\",\n      description: \"Sprint 3 retro — focus on deployment pipeline improvements\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"f29b\",\n      \"Tech Talk\",\n      d(2, 27, 14, 30),\n      d(2, 27, 15, 30),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        description: \"Internal talk on building accessible UI components\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"f29c\", \"Friday Wrap-up\", d(2, 27, 16), d(2, 27, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"f29d\", \"Happy Hour\", d(2, 27, 17), d(2, 27, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      description: \"End-of-sprint celebration\",\n      location: \"The Draft House\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"f30\", \"Gym\", d(2, 26, 18), d(2, 26, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      description: \"Yoga + meditation session\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n\n    // ── March 2026 ──\n\n    // Week of Mar 1 (Sun Mar 1 – Sat Mar 7)\n    ev(\n      \"m01\",\n      \"Team Standup\",\n      d(3, 2, 9),\n      d(3, 2, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m02\",\n      \"March Priorities\",\n      d(3, 2, 10),\n      d(3, 2, 11, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description:\n          \"Set engineering priorities for March — focus on performance and reliability\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m03\",\n      \"Vendor Meeting\",\n      d(3, 3, 13),\n      d(3, 3, 14),\n      \"red\",\n      \"you@example.com\",\n      {\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m03b\",\n      \"1:1 with Manager\",\n      d(3, 4, 15),\n      d(3, 4, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Q2 role expectations discussion\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m04\",\n      \"Lunch with Sarah\",\n      d(3, 4, 12),\n      d(3, 4, 13),\n      \"purple\",\n      \"Personal\",\n      {\n        description: \"She got the new job! Celebration lunch\",\n        location: \"Sushi Nakazawa\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"m05\", \"Workshop: Testing\", d(3, 5, 9), d(3, 5, 12), \"blue\", \"Work\", {\n      description:\n        \"Testing Best Practices — unit tests, integration tests, E2E with Playwright\",\n      location: \"Room 4B\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m06\", \"Gym\", d(3, 5, 18), d(3, 5, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m06b\", \"Friday Wrap-up\", d(3, 6, 16), d(3, 6, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m06c\", \"Happy Hour\", d(3, 6, 17), d(3, 6, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      location: \"The Draft House\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m07\", \"Game Night\", d(3, 6, 19), d(3, 6, 22), \"orange\", \"Family\", {\n      description:\n        \"Board games at our place — picking up snacks on the way home\",\n      reminders: [{ amount: 2, unit: \"hours\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n\n    // Week of Mar 8 (Sun Mar 8 – Sat Mar 14) — triple-booked Tuesday\n    ev(\n      \"m08\",\n      \"Team Standup\",\n      d(3, 9, 9),\n      d(3, 9, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"m09\", \"OKR Review\", d(3, 9, 10), d(3, 9, 11, 30), \"blue\", \"Work\", {\n      description: \"Mid-quarter OKR progress review with leadership\",\n      reminders: [{ amount: 15, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"m09b\",\n      \"Eng All-Hands\",\n      d(3, 9, 10),\n      d(3, 9, 11),\n      \"red\",\n      \"you@example.com\",\n      {\n        description:\n          \"Engineering org all-hands — CTO presenting new tech strategy\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m10\",\n      \"Client Call\",\n      d(3, 10, 14),\n      d(3, 10, 15),\n      \"red\",\n      \"you@example.com\",\n      {\n        description:\n          \"Acme Corp escalation — discuss SLA concerns and resolution plan\",\n        reminders: [\n          { amount: 10, unit: \"minutes\" },\n          { amount: 1, unit: \"hours\" },\n        ],\n        calendarEmail: \"you@example.com\",\n        timezone: \"GMT-3 Sao Paulo\",\n      },\n    ),\n    ev(\n      \"m10b\",\n      \"Sales Enablement\",\n      d(3, 10, 13, 30),\n      d(3, 10, 14, 30),\n      \"orange\",\n      \"Family\",\n      {\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m10c\",\n      \"Database Migration Plan\",\n      d(3, 10, 14),\n      d(3, 10, 16),\n      \"blue\",\n      \"Work\",\n      {\n        description: \"Plan PostgreSQL → CockroachDB migration with infra team\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m11\",\n      \"1:1 with Manager\",\n      d(3, 11, 15),\n      d(3, 11, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Discuss tech lead role transition and team restructuring\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m11b\",\n      \"Incident Post-mortem\",\n      d(3, 11, 14, 30),\n      d(3, 11, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Review Tuesday's production outage — identify root cause\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"m11c\",\n      \"Frontend Guild\",\n      d(3, 11, 15),\n      d(3, 11, 16),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        description: \"Monthly frontend guild — discuss React 19 migration plan\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m12\",\n      \"Design Review\",\n      d(3, 12, 11),\n      d(3, 12, 12, 30),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        description:\n          \"Review new dark mode color palette for the component library\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"m12b\", \"Gym\", d(3, 12, 18), d(3, 12, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m13\", \"Team Offsite\", d(3, 12, 0), d(3, 13, 0), \"blue\", \"Work\", {\n      isAllDay: true,\n      description: \"Annual team offsite — team building and strategy sessions\",\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m13b\", \"Friday Wrap-up\", d(3, 13, 16), d(3, 13, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m14\", \"Happy Hour\", d(3, 13, 17), d(3, 13, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Post-offsite drinks to unwind\",\n      location: \"The Pub\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n\n    // Week of Mar 15 (Sun Mar 15 – Sat Mar 21)\n    ev(\n      \"m15\",\n      \"Team Standup\",\n      d(3, 16, 9),\n      d(3, 16, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m16\",\n      \"Sprint Planning\",\n      d(3, 16, 10),\n      d(3, 16, 11, 30),\n      \"blue\",\n      \"Work\",\n      {\n        recurrence: \"Every 2 weeks on Mon\",\n        description: \"Sprint 5 planning — final sprint before Q1 wrap\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m17\",\n      \"Lunch with Alex\",\n      d(3, 17, 12),\n      d(3, 17, 13),\n      \"purple\",\n      \"Personal\",\n      {\n        description: \"Trying the new Thai place he keeps recommending\",\n        location: \"Thai Basil\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m17b\",\n      \"1:1 with Manager\",\n      d(3, 18, 15),\n      d(3, 18, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Annual review prep discussion\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m18\",\n      \"Perf Review Prep\",\n      d(3, 18, 14),\n      d(3, 18, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        description:\n          \"Write self-review and gather peer feedback for annual review cycle\",\n        reminders: [{ amount: 30, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m19\",\n      \"Side Project Sync\",\n      d(3, 19, 13),\n      d(3, 19, 14),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"m20\", \"Gym\", d(3, 19, 18), d(3, 19, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      description: \"Spin class + abs\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m20b\", \"Friday Wrap-up\", d(3, 20, 16), d(3, 20, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m20c\", \"Happy Hour\", d(3, 20, 17), d(3, 20, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      location: \"Irish Pub\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"m21\",\n      \"St. Patrick's Day\",\n      d(3, 17, 0),\n      d(3, 17, 0),\n      \"green\",\n      \"Holidays in Brazil\",\n      { isAllDay: true, calendarEmail: \"you@example.com\" },\n    ),\n\n    // Week of Mar 22 (Sun Mar 22 – Sat Mar 28)\n    ev(\n      \"m22\",\n      \"Team Standup\",\n      d(3, 23, 9),\n      d(3, 23, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"m23\", \"Q1 Wrap-up\", d(3, 23, 10), d(3, 23, 12), \"blue\", \"Work\", {\n      description:\n        \"Final Q1 summary meeting — present achievements, lessons learned, and Q2 outlook\",\n      reminders: [\n        { amount: 15, unit: \"minutes\" },\n        { amount: 1, unit: \"days\" },\n      ],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"m24\",\n      \"Client Demo\",\n      d(3, 24, 14),\n      d(3, 24, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Final demo of the new reporting dashboard to Acme Corp\",\n        reminders: [{ amount: 30, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n        status: \"busy\",\n      },\n    ),\n    ev(\n      \"m24b\",\n      \"1:1 with Manager\",\n      d(3, 25, 15),\n      d(3, 25, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Q1 wrap-up and Q2 expectations\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m25\",\n      \"Architecture Review\",\n      d(3, 25, 13),\n      d(3, 25, 14, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description: \"Review database sharding proposal and caching strategy\",\n        reminders: [{ amount: 15, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"m26\", \"Sprint Review\", d(3, 26, 10), d(3, 26, 11), \"blue\", \"Work\", {\n      recurrence: \"Every 2 weeks on Thu\",\n      description: \"Demo sprint 5 features — focus on performance improvements\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m27\", \"Retro\", d(3, 27, 14), d(3, 27, 15), \"blue\", \"Work\", {\n      recurrence: \"Every 2 weeks on Fri\",\n      description:\n        \"Q1 final retro — what worked, what didn't, action items for Q2\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m27b\", \"Friday Wrap-up\", d(3, 27, 16), d(3, 27, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m27c\", \"Happy Hour\", d(3, 27, 17), d(3, 27, 19), \"purple\", \"Personal\", {\n      recurrence: \"Every week on Fri\",\n      description: \"End-of-quarter celebration drinks\",\n      location: \"The Rooftop\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m28\", \"Gym\", d(3, 26, 18), d(3, 26, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      description: \"Boxing class + cool down\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"m29\",\n      \"Birthday Party\",\n      d(3, 28, 15),\n      d(3, 28, 18),\n      \"orange\",\n      \"Family\",\n      {\n        description: \"Nephew's 5th birthday — bring the Lego set we got him\",\n        location: \"Fun Zone\",\n        reminders: [\n          { amount: 2, unit: \"hours\" },\n          { amount: 1, unit: \"days\" },\n        ],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n\n    // Week of Mar 29 (Sun Mar 29 – Sat Apr 4)\n    ev(\n      \"m30\",\n      \"Team Standup\",\n      d(3, 30, 9),\n      d(3, 30, 9, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Mon\",\n        description: \"Daily sync — blockers, progress, priorities\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"m31\", \"Q2 Planning\", d(3, 30, 10), d(3, 30, 12), \"blue\", \"Work\", {\n      description:\n        \"Kick off Q2 planning — define themes, allocate resources, set milestones\",\n      reminders: [\n        { amount: 15, unit: \"minutes\" },\n        { amount: 1, unit: \"days\" },\n      ],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"m31b\",\n      \"1:1 with Manager\",\n      d(4, 1, 15),\n      d(4, 1, 15, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        recurrence: \"Every week on Wed\",\n        description: \"Q2 kickoff goals alignment\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\"m31c\", \"Gym\", d(4, 2, 18), d(4, 2, 19, 30), \"green\", \"Fitness\", {\n      recurrence: \"Every week on Thu\",\n      reminders: [{ amount: 30, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\"m31d\", \"Friday Wrap-up\", d(4, 3, 16), d(4, 3, 17), \"blue\", \"Work\", {\n      recurrence: \"Every week on Fri\",\n      description: \"Review weekly accomplishments and set Monday priorities\",\n      reminders: [{ amount: 10, unit: \"minutes\" }],\n      calendarEmail: \"you@example.com\",\n    }),\n    ev(\n      \"m32\",\n      \"Month-End Report\",\n      d(3, 31, 10),\n      d(3, 31, 11, 30),\n      \"red\",\n      \"you@example.com\",\n      {\n        description:\n          \"Compile March metrics, budget reconciliation, and Q1 summary for finance\",\n        reminders: [{ amount: 1, unit: \"hours\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n\n    // ── Historical Sprint events (for search testing) ──\n    ev(\n      \"h01\",\n      \"Sprint Kickoff — Q2 Platform Migration Initiative\",\n      dy(2024, 6, 10, 9),\n      dy(2024, 6, 10, 10, 30),\n      \"blue\",\n      \"Work\",\n      {\n        description: \"Q2 2024 sprint kickoff — defining team velocity baseline\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"h02\",\n      \"Sprint Retrospective — Process Improvements and Velocity Analysis\",\n      dy(2025, 3, 14, 14),\n      dy(2025, 3, 14, 15),\n      \"blue\",\n      \"Work\",\n      {\n        description: \"Q1 2025 sprint retro — process improvements discussion\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n\n    // ── Extra Sprint events on Mon Feb 23 2026 ──\n    ev(\n      \"f25c\",\n      \"Sprint Demo — Presenting New Dashboard Features to Stakeholders\",\n      d(2, 23, 14),\n      d(2, 23, 15),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Demo sprint 4 deliverables to product team\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"f25d\",\n      \"Sprint Retro\",\n      d(2, 23, 15, 30),\n      d(2, 23, 16, 30),\n      \"yellow\",\n      \"Side Projects\",\n      {\n        description: \"Sprint 4 retrospective — team feedback session\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n\n    // ── Sprint events on Mar 18 and Mar 19 2026 ──\n    ev(\n      \"m17c\",\n      \"Sprint Standup\",\n      d(3, 18, 9, 30),\n      d(3, 18, 10),\n      \"red\",\n      \"you@example.com\",\n      {\n        description: \"Mid-sprint sync — check blockers and progress\",\n        reminders: [{ amount: 5, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n    ev(\n      \"m19b\",\n      \"Sprint Grooming — Backlog Refinement and Story Estimation Session\",\n      d(3, 19, 10),\n      d(3, 19, 11),\n      \"blue\",\n      \"Work\",\n      {\n        description:\n          \"Backlog grooming for next sprint — estimate and prioritize stories\",\n        reminders: [{ amount: 10, unit: \"minutes\" }],\n        calendarEmail: \"you@example.com\",\n      },\n    ),\n  ];\n}\n",
      "type": "registry:lib",
      "target": "lib/layouts/mock-events.ts"
    },
    {
      "path": "app/preview/layouts/calendar/page.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport Link from \"next/link\";\nimport {\n  ArrowLeftIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  PanelLeftIcon,\n  PanelRightIcon,\n} from \"lucide-react\";\n\nimport { addDays, addWeeks, format, startOfDay, startOfWeek } from \"date-fns\";\nimport { useTheme } from \"next-themes\";\nimport { generateMockEvents } from \"@/lib/layouts/mock-events\";\nimport { CommandMenu } from \"@/components/layouts/calendar/command-menu\";\nimport { SidebarLeft } from \"@/components/layouts/calendar/sidebar-left\";\nimport type {\n  CalendarEvent,\n  ViewSettings,\n  ViewType,\n} from \"@/components/layouts/calendar/week-view-types\";\nimport { SidebarRight } from \"@/components/layouts/calendar/sidebar-right\";\nimport {\n  WeekView,\n  getCalendarHeaderInfo,\n  getVisibleDays,\n} from \"@/components/layouts/calendar/week-view\";\nimport { Avatar, AvatarFallback } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { ViewDropdown } from \"@/components/layouts/calendar/view-dropdown\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n  SidebarInset,\n  SidebarProvider,\n  useSidebar,\n} from \"@/components/ui/sidebar\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { Kbd } from \"@/components/ui/kbd\";\n\nfunction PageContent() {\n  const { theme, setTheme } = useTheme();\n  const [leftSidebarOpen, setLeftSidebarOpen] = React.useState(true);\n  const [view, setView] = React.useState<ViewType>(\"week\");\n  const [currentDate, setCurrentDate] = React.useState(() =>\n    startOfWeek(new Date(), { weekStartsOn: 0 }),\n  );\n  const [events, setEvents] = React.useState(() => generateMockEvents());\n  const [selectedEventId, setSelectedEventId] = React.useState<string | null>(\n    null,\n  );\n  const [commandMenuOpen, setCommandMenuOpen] = React.useState(false);\n  const [numberOfDays, setNumberOfDays] = React.useState(7);\n  const [viewSettings, setViewSettings] = React.useState<ViewSettings>({\n    showWeekends: true,\n    showDeclinedEvents: true,\n    showWeekNumbers: true,\n  });\n  const selectedEvent = React.useMemo(\n    () => events.find((e) => e.id === selectedEventId) ?? null,\n    [events, selectedEventId],\n  );\n\n  const handleEventChange = React.useCallback((updatedEvent: CalendarEvent) => {\n    setEvents((prev) =>\n      prev.map((e) => (e.id === updatedEvent.id ? updatedEvent : e)),\n    );\n  }, []);\n\n  const goToToday = React.useCallback(() => {\n    if (view === \"day\") {\n      setCurrentDate(startOfDay(new Date()));\n    } else {\n      setCurrentDate(startOfWeek(new Date(), { weekStartsOn: 0 }));\n    }\n  }, [view]);\n\n  const goToPrev = React.useCallback(() => {\n    if (view === \"day\") {\n      setCurrentDate((prev) => addDays(prev, -1));\n    } else {\n      setCurrentDate((prev) => addWeeks(prev, -1));\n    }\n  }, [view]);\n\n  const goToNext = React.useCallback(() => {\n    if (view === \"day\") {\n      setCurrentDate((prev) => addDays(prev, 1));\n    } else {\n      setCurrentDate((prev) => addWeeks(prev, 1));\n    }\n  }, [view]);\n\n  const goToDate = React.useCallback((date: Date) => setCurrentDate(date), []);\n\n  const goToDateWeek = React.useCallback(\n    (date: Date) => {\n      if (view === \"day\") {\n        setCurrentDate(startOfDay(date));\n        return;\n      }\n      // Center the selected date inside the 7-day visible window so the\n      // timeline shifts to place the clicked day in the middle column\n      // (positions: [date-3, date-2, date-1, date, date+1, date+2, date+3]).\n      // The week view always renders VISIBLE_DAYS_BY_VIEW.week = 7 days\n      // starting from `currentDate`, so pulling back by 3 lands the click\n      // in slot 4 of 7.\n      setCurrentDate(addDays(startOfDay(date), -3));\n    },\n    [view],\n  );\n\n  const switchView = React.useCallback(\n    (newView: ViewType) => {\n      if (newView === view) return;\n      setView(newView);\n      if (newView === \"day\") {\n        setCurrentDate(startOfDay(new Date()));\n        return;\n      }\n      setCurrentDate((prev) => startOfWeek(prev, { weekStartsOn: 0 }));\n    },\n    [view],\n  );\n\n  const toggleWeekends = React.useCallback(() => {\n    setViewSettings((prev) => ({ ...prev, showWeekends: !prev.showWeekends }));\n  }, []);\n\n  const toggleDeclinedEvents = React.useCallback(() => {\n    setViewSettings((prev) => ({\n      ...prev,\n      showDeclinedEvents: !prev.showDeclinedEvents,\n    }));\n  }, []);\n\n  const toggleWeekNumbers = React.useCallback(() => {\n    setViewSettings((prev) => ({\n      ...prev,\n      showWeekNumbers: !prev.showWeekNumbers,\n    }));\n  }, []);\n\n  const cycleTheme = React.useCallback(() => {\n    if (theme === \"system\") {\n      setTheme(\"light\");\n      return;\n    }\n    if (theme === \"light\") {\n      setTheme(\"dark\");\n      return;\n    }\n    setTheme(\"system\");\n  }, [theme, setTheme]);\n\n  const { toggleSidebar, open: rightSidebarOpen } = useSidebar();\n\n  const [visibleDays, setVisibleDays] = React.useState<Date[]>(() =>\n    getVisibleDays(currentDate, view),\n  );\n\n  const { monthName, year, weekNumber } = getCalendarHeaderInfo(\n    visibleDays[0],\n    0,\n  );\n\n  // Keyboard shortcuts (macOS conventions — symbols shown in Kbd hints are\n  // ⌘ ⇧ ⌥ ⌃ ⎋). Handlers still accept metaKey || ctrlKey so Windows/Linux\n  // users aren't locked out, but the displayed symbols are Mac-first.\n  React.useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      const mod = e.metaKey || e.ctrlKey; // ⌘ on Mac, Ctrl on Win/Linux\n      const key = e.key.length === 1 ? e.key.toLowerCase() : e.key;\n\n      // ⌘K — command menu\n      if (mod && !e.shiftKey && !e.altKey && key === \"k\") {\n        e.preventDefault();\n        setCommandMenuOpen((prev) => !prev);\n        return;\n      }\n      // ⌘/ — toggle left sidebar (mini-calendar column)\n      if (mod && !e.shiftKey && !e.altKey && key === \"/\") {\n        e.preventDefault();\n        setLeftSidebarOpen((prev) => !prev);\n        return;\n      }\n      // ⌘⇧E — toggle weekends\n      if (mod && e.shiftKey && key === \"e\") {\n        e.preventDefault();\n        toggleWeekends();\n        return;\n      }\n      // ⌘⇧D — toggle declined events\n      if (mod && e.shiftKey && key === \"d\") {\n        e.preventDefault();\n        toggleDeclinedEvents();\n        return;\n      }\n      // ⌘⇧L — cycle appearance (Safari / macOS-idiomatic for theme toggle)\n      if (mod && e.shiftKey && key === \"l\") {\n        e.preventDefault();\n        cycleTheme();\n        return;\n      }\n      // ⎋ — deselect the focused event\n      if (e.key === \"Escape\") {\n        setSelectedEventId(null);\n        return;\n      }\n\n      // Below this point: bare-key shortcuts (Notion-Calendar style). Skip\n      // if focus is in a text field so typing stays typing.\n      const target = e.target as HTMLElement;\n      if (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\") {\n        return;\n      }\n      if (mod || e.altKey) return; // don't catch bare-key path on modifiers\n\n      // / — toggle right sidebar (context panel)\n      if (key === \"/\") {\n        e.preventDefault();\n        toggleSidebar();\n        return;\n      }\n      // T — jump to today\n      if (key === \"t\") {\n        e.preventDefault();\n        goToToday();\n        return;\n      }\n      // D or 1 — day view\n      if (key === \"d\" || key === \"1\") {\n        e.preventDefault();\n        switchView(\"day\");\n        return;\n      }\n      // W or 0 — week view\n      if (key === \"w\" || key === \"0\") {\n        e.preventDefault();\n        switchView(\"week\");\n        return;\n      }\n      // M — month view\n      if (key === \"m\") {\n        e.preventDefault();\n        switchView(\"month\");\n        return;\n      }\n      // 2–9 — set visible day count\n      if (/^[2-9]$/.test(key)) {\n        e.preventDefault();\n        setNumberOfDays(Number(key));\n        return;\n      }\n      // J or ← — previous period\n      if (key === \"j\" || key === \"ArrowLeft\") {\n        e.preventDefault();\n        goToPrev();\n        return;\n      }\n      // K or → — next period\n      if (key === \"k\" || key === \"ArrowRight\") {\n        e.preventDefault();\n        goToNext();\n        return;\n      }\n    };\n\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleKeyDown);\n  }, [\n    toggleSidebar,\n    goToToday,\n    goToPrev,\n    goToNext,\n    switchView,\n    toggleWeekends,\n    toggleDeclinedEvents,\n    cycleTheme,\n  ]);\n\n  return (\n    <>\n      <CommandMenu\n        open={commandMenuOpen}\n        onOpenChange={setCommandMenuOpen}\n        onGoToToday={goToToday}\n        onGoToPrev={goToPrev}\n        onGoToNext={goToNext}\n        onSwitchView={switchView}\n        onToggleLeftSidebar={() => setLeftSidebarOpen((prev) => !prev)}\n        onToggleRightSidebar={toggleSidebar}\n        onCycleTheme={cycleTheme}\n      />\n      <SidebarRight\n        open={leftSidebarOpen}\n        onDateSelect={goToDateWeek}\n        currentDate={currentDate}\n        visibleDays={visibleDays}\n      />\n      <SidebarInset className=\"flex flex-col overflow-hidden\">\n        <header className=\"bg-background sticky top-0 z-30 flex h-14 shrink-0 items-center gap-2\">\n          <div className=\"flex flex-1 items-center gap-2 px-4\">\n            <Link\n              href=\"/layouts\"\n              className=\"inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground transition-colors hover:text-foreground\"\n            >\n              <ArrowLeftIcon className=\"h-3 w-3\" />\n              Layouts\n            </Link>\n            <Separator\n              orientation=\"vertical\"\n              className=\"mx-1 data-[orientation=vertical]:h-4\"\n            />\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"size-7 text-muted-foreground\"\n                  onClick={() => setLeftSidebarOpen((prev) => !prev)}\n                >\n                  <PanelLeftIcon />\n                  <span className=\"sr-only\">Toggle Calendar Sidebar</span>\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"bottom\">\n                {leftSidebarOpen ? \"Close\" : \"Open\"} sidebar{\" \"}\n                <Kbd className=\"ml-1\">⌘</Kbd> <Kbd>/</Kbd>\n              </TooltipContent>\n            </Tooltip>\n            <Separator\n              orientation=\"vertical\"\n              className=\"mr-2 data-[orientation=vertical]:h-4\"\n            />\n            <h1 className=\"text-xl\">\n              <span className=\"font-extrabold\">{monthName}</span>{\" \"}\n              <span className=\"font-extrabold\">{year}</span>{\" \"}\n              <span className=\"text-muted-foreground text-xs\">\n                {view === \"day\" && format(currentDate, \"EEEE, MMM d\")}\n                {view === \"week\" && `Week ${weekNumber}`}\n                {view === \"month\" && format(currentDate, \"MMMM\")}\n              </span>\n            </h1>\n          </div>\n          <div className=\"flex items-center gap-2 px-4\">\n            <Avatar className=\"size-7\">\n              <AvatarFallback>RX</AvatarFallback>\n            </Avatar>\n            <ViewDropdown\n              view={view}\n              numberOfDays={numberOfDays}\n              viewSettings={viewSettings}\n              onSwitchView={switchView}\n              onSetNumberOfDays={setNumberOfDays}\n              onToggleWeekends={toggleWeekends}\n              onToggleDeclinedEvents={toggleDeclinedEvents}\n              onToggleWeekNumbers={toggleWeekNumbers}\n            />\n            <Button\n              variant=\"secondary\"\n              size=\"sm\"\n              className=\"px-3\"\n              onClick={goToToday}\n            >\n              Today\n            </Button>\n            <div className=\"flex items-center\">\n              <Button\n                variant=\"ghost\"\n                size=\"icon\"\n                className=\"size-8 text-muted-foreground\"\n                onClick={goToPrev}\n              >\n                <ChevronLeftIcon className=\"size-4\" />\n                <span className=\"sr-only\">\n                  {view === \"day\" ? \"Previous day\" : \"Previous week\"}\n                </span>\n              </Button>\n              <Button\n                variant=\"ghost\"\n                size=\"icon\"\n                className=\"size-8 text-muted-foreground\"\n                onClick={goToNext}\n              >\n                <ChevronRightIcon className=\"size-4\" />\n                <span className=\"sr-only\">\n                  {view === \"day\" ? \"Next day\" : \"Next week\"}\n                </span>\n              </Button>\n            </div>\n            {!rightSidebarOpen && (\n              <Tooltip>\n                <TooltipTrigger asChild>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    className=\"size-7 text-muted-foreground\"\n                    onClick={toggleSidebar}\n                  >\n                    <PanelRightIcon />\n                    <span className=\"sr-only\">Toggle Navigation Sidebar</span>\n                  </Button>\n                </TooltipTrigger>\n                <TooltipContent side=\"bottom\">\n                  Open context panel <Kbd className=\"ml-1\">/</Kbd>\n                </TooltipContent>\n              </Tooltip>\n            )}\n          </div>\n        </header>\n        <div className=\"flex flex-1 flex-col overflow-hidden\">\n          <WeekView\n            view={view}\n            currentDate={currentDate}\n            events={events}\n            onEventClick={(e) => setSelectedEventId(e.id)}\n            selectedEventId={selectedEvent?.id}\n            onBackgroundClick={() => setSelectedEventId(null)}\n            onDateChange={goToDate}\n            onVisibleDaysChange={setVisibleDays}\n            onEventChange={handleEventChange}\n            isSidebarOpen={rightSidebarOpen}\n            onDockToSidebar={() => {\n              if (!rightSidebarOpen) toggleSidebar();\n            }}\n            onClosePopover={() => setSelectedEventId(null)}\n            onPrevWeek={goToPrev}\n            onNextWeek={goToNext}\n          />\n        </div>\n      </SidebarInset>\n      <SidebarLeft\n        events={events}\n        selectedEvent={selectedEvent}\n        onEventChange={handleEventChange}\n        onPrevWeek={goToPrev}\n        onNextWeek={goToNext}\n      />\n    </>\n  );\n}\n\nexport default function CalendarLayoutPage() {\n  return (\n    <SidebarProvider className=\"h-screen\">\n      <PageContent />\n    </SidebarProvider>\n  );\n}\n",
      "type": "registry:page",
      "target": "app/calendar/page.tsx"
    }
  ]
}