{
  "slug": "calendar-card",
  "name": "Calendar Card",
  "description": "A tactile calendar with animated date selection, event dots, hover event popups and an optional event creation form.",
  "summary": "Select dates and browse months or years. Hover, focus or tap a marked date for event details, and use the optional add form to schedule events and assign them to a name or team.",
  "category": "date-and-time",
  "tags": [
    "calendar",
    "date",
    "picker",
    "card",
    "gsap",
    "animation"
  ],
  "dependencies": [
    "gsap",
    "@gsap/react",
    "lucide-react",
    "@radix-ui/react-dialog"
  ],
  "registryDependencies": [
    "utils"
  ],
  "version": "1.0.0",
  "createdDate": "2026-10-10",
  "updatedDate": "2026-10-11",
  "interactive": true,
  "highlights": [
    "Raised charcoal card with coral accents",
    "Monday-first calendar with keyboard navigation",
    "Inline month and year selection",
    "Clamped month navigation preserves the day where possible",
    "GSAP transitions with reduced-motion support",
    "Horizontal layout in wide containers, compact stacked layout on mobile",
    "Rolling day transitions and independent counter animation for changing year digits",
    "Translucent date hover highlight glides between cells without changing selection",
    "Optional event click callback for opening event details",
    "Customizable accent, comfortable or compact spacing, and Monday or Sunday week start",
    "Event details in an animated popup on hover, keyboard focus or tap",
    "Optional event form with date, time, color and assignee fields",
    "Events remain owned by the caller through events and onEventAdd props"
  ],
  "accessibility": {
    "role": "group",
    "aria": "Labeled date buttons expose selection and event counts. Marked dates expose an event popup. The add form uses an accessible modal with labeled fields and focus restoration.",
    "reducedMotion": "Animations are disabled when prefers-reduced-motion is enabled.",
    "keyboard": [
      {
        "key": "Arrow keys",
        "description": "Select the adjacent day or week."
      },
      {
        "key": "PageUp / PageDown",
        "description": "Select the same day in the previous or next month, clamped to the last day."
      },
      {
        "key": "Home / End",
        "description": "Select the first or last day of the month."
      },
      {
        "key": "Escape",
        "description": "Close the event popup or month picker. Escape also closes the add-event modal."
      },
      {
        "key": "Focus / Tab",
        "description": "Focusing a marked date opens its details. Tab into the popup to interact with events or add another event."
      }
    ]
  },
  "props": [
    {
      "name": "value",
      "type": "Date",
      "description": "Controlled selected date, using local calendar days."
    },
    {
      "name": "defaultValue",
      "type": "Date",
      "defaultValue": "Today",
      "description": "Initial date for uncontrolled usage."
    },
    {
      "name": "onChange",
      "type": "(date: Date) => void",
      "description": "Called when a date is selected or month navigation changes the selection."
    },
    {
      "name": "className",
      "type": "string",
      "description": "Additional card classes."
    },
    {
      "name": "events",
      "type": "CalendarEvent[]",
      "defaultValue": "[]",
      "description": "Events with a unique id, local YYYY-MM-DD date, title, optional time label, CSS color and assignee. Up to three dots per date; its popup lists all events in input order."
    },
    {
      "name": "onEventClick",
      "type": "(event: CalendarEvent) => void",
      "description": "Makes popup event entries clickable and receives the clicked event."
    },
    {
      "name": "accentColor",
      "type": "string",
      "defaultValue": "#ff675c",
      "description": "CSS accent color for selection, progress, focus rings and uncolored events. Use a light accent with sufficient contrast against the dark selected-date text."
    },
    {
      "name": "density",
      "type": "\"comfortable\" | \"compact\"",
      "defaultValue": "comfortable",
      "description": "Spacing of date cells. The selection and hover highlights follow the cell size."
    },
    {
      "name": "weekStartsOn",
      "type": "0 | 1",
      "defaultValue": "1",
      "description": "First weekday: 0 for Sunday or 1 for Monday."
    },
    {
      "name": "showEvents",
      "type": "boolean",
      "defaultValue": "true",
      "description": "Enables event detail popups on hover, focus or tap. Date dots remain visible when disabled."
    },
    {
      "name": "showProgress",
      "type": "boolean",
      "defaultValue": "true",
      "description": "Shows segmented progress through the selected month."
    },
    {
      "name": "onEventAdd",
      "type": "(event: CalendarEvent) => void",
      "description": "Enables the Add event form and receives the new event with a generated id. Append it to your events state or persist it in your app. The form includes an optional assignee name or team; it does not send invitations."
    }
  ],
  "usage": "\"use client\";\n\nimport { useState } from \"react\";\nimport { CalendarCard, type CalendarEvent } from \"@/components/ui/calendar-card\";\n\nexport default function Example() {\n  const [events, setEvents] = useState<CalendarEvent[]>([\n    { id: \"review\", date: \"2026-10-11\", title: \"Design review\", time: \"10:30\", assignee: \"Design team\", color: \"#fbbf24\" },\n  ]);\n  return (\n    <CalendarCard\n      defaultValue={new Date(2026, 9, 11)}\n      events={events}\n      onEventAdd={(event) => setEvents((current) => [...current, event])}\n      onEventClick={(event) => console.log(event)}\n    />\n  );\n}",
  "files": [
    {
      "name": "calendar-card.tsx",
      "path": "src/registry/ui/calendar-card.tsx",
      "code": "\"use client\";\n\nimport {\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type KeyboardEvent,\n  type CSSProperties,\n} from \"react\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { ChevronDown, ChevronLeft, ChevronRight, Plus, X } from \"lucide-react\";\nimport * as Dialog from \"@radix-ui/react-dialog\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport interface CalendarEvent {\n  id: string;\n  date: string;\n  title: string;\n  time?: string;\n  color?: string;\n  assignee?: string;\n}\n\nexport interface CalendarCardProps {\n  value?: Date;\n  defaultValue?: Date;\n  onChange?: (date: Date) => void;\n  className?: string;\n  events?: CalendarEvent[];\n  onEventClick?: (event: CalendarEvent) => void;\n  onEventAdd?: (event: CalendarEvent) => void;\n  accentColor?: string;\n  density?: \"comfortable\" | \"compact\";\n  weekStartsOn?: 0 | 1;\n  showEvents?: boolean;\n  showProgress?: boolean;\n}\n\nconst months = [\n  \"January\", \"February\", \"March\", \"April\", \"May\", \"June\",\n  \"July\", \"August\", \"September\", \"October\", \"November\", \"December\",\n];\nconst weekdays = [\n  \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\",\n];\nconst subscribe = () => () => {};\nconst readToday = () => new Date().setHours(0, 0, 0, 0);\nconst serverToday = () => 0;\n\nfunction inMonth(date: Date, year: number, month: number) {\n  return new Date(\n    year,\n    month,\n    Math.min(date.getDate(), new Date(year, month + 1, 0).getDate()),\n  );\n}\n\nexport function CalendarCard({\n  value, defaultValue, onChange, className, events = [], onEventClick, onEventAdd,\n  accentColor = \"#ff675c\", density = \"comfortable\", weekStartsOn = 1,\n  showEvents = true, showProgress = true,\n}: CalendarCardProps) {\n  const today = useSyncExternalStore(subscribe, readToday, serverToday);\n  const [internalDate, setInternalDate] = useState(defaultValue);\n  const date = value ?? internalDate ?? new Date(today);\n  const ready = Boolean(value || internalDate || today);\n  const year = date.getFullYear();\n  const month = date.getMonth();\n  const day = date.getDate();\n  const [pickerYear, setPickerYear] = useState<number | null>(null);\n  const rootRef = useRef<HTMLDivElement>(null);\n  const popupRef = useRef<HTMLDivElement>(null);\n  const eventTitleRef = useRef<HTMLInputElement>(null);\n  const eventCounter = useRef(0);\n  const popupAnchor = useRef<HTMLButtonElement | null>(null);\n  const closePopupTween = useRef<gsap.core.Tween | null>(null);\n  const [popupDay, setPopupDay] = useState<number | null>(null);\n  const [eventFormDate, setEventFormDate] = useState<string | null>(null);\n  const titleRef = useRef<HTMLButtonElement>(null);\n  const keyboardFocus = useRef(false);\n  const previousNumbers = useRef({ day, year: pickerYear });\n  const previousHighlight = useRef<HTMLElement | null>(null);\n  const panelId = useId();\n  const popupId = useId();\n  const count = new Date(year, month + 1, 0).getDate();\n  const offset = (new Date(year, month, 1).getDay() - weekStartsOn + 7) % 7;\n  const week = weekStartsOn === 1 ? weekdays : [weekdays[6], ...weekdays.slice(0, 6)];\n  const monthKey = `${year}-${String(month + 1).padStart(2, \"0\")}`;\n  const eventsByDay = new Map<number, CalendarEvent[]>();\n  for (const event of events) {\n    if (!event.date.startsWith(`${monthKey}-`)) continue;\n    const eventDay = Number(event.date.slice(monthKey.length + 1));\n    if (event.date !== `${monthKey}-${String(eventDay).padStart(2, \"0\")}` || eventDay < 1 || eventDay > count) continue;\n    const entries = eventsByDay.get(eventDay) ?? [];\n    entries.push(event);\n    eventsByDay.set(eventDay, entries);\n  }\n  const selectedEvents = eventsByDay.get(day) ?? [];\n  const popupEvents = eventsByDay.get(popupDay ?? 0) ?? [];\n  const popupOpen = showEvents && pickerYear === null && popupDay !== null && popupEvents.length > 0;\n  const dateKey = `${monthKey}-${String(day).padStart(2, \"0\")}`;\n  const EventTag = onEventClick ? \"button\" : \"div\";\n  const selectedLabel = `${months[month]} ${day}, ${year}`;\n  const buttonClass = \"cursor-pointer rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-[var(--calendar-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[#191919]\";\n\n  useGSAP(\n    () => {\n      if (!ready) return;\n      const media = gsap.matchMedia();\n      media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n        gsap.from(\"[data-calendar-panel]\", {\n          y: 8, opacity: 0, duration: 0.28,\n          ease: \"power3.out\", clearProps: \"transform,opacity\",\n        });\n      }, rootRef);\n      return () => media.revert();\n    },\n    {\n      scope: rootRef,\n      dependencies: [year, month, pickerYear !== null, ready],\n      revertOnUpdate: true,\n    },\n  );\n\n  useGSAP(() => {\n    const previous = previousNumbers.current;\n    previousNumbers.current = { day, year: pickerYear };\n    if (!ready) return;\n    const media = gsap.matchMedia();\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n      for (const [name, before, after] of [\n        [\"day\", previous.day, day],\n        [\"year\", previous.year, pickerYear],\n      ] as const) {\n        if (before === null || after === null || before === after) continue;\n        const direction = after > before ? 1 : -1;\n        const duration = name === \"year\" ? 0.38 : 0.3;\n        const slots = rootRef.current?.querySelectorAll<HTMLElement>(\n          name === \"year\" ? \"[data-calendar-year-digit]\" : \"[data-calendar-day-number]\",\n        );\n        slots?.forEach((slot) => {\n          const place = Number(slot.dataset.calendarYearDigit ?? 0);\n          const oldText = name === \"year\" ? String(before).at(-place - 1) ?? \"\" : String(before);\n          const newText = name === \"year\" ? String(after).at(-place - 1) ?? \"\" : String(after);\n          if (oldText === newText) return;\n          const outgoing = slot.querySelector<HTMLElement>(`[data-calendar-${name}-out]`);\n          const incoming = slot.querySelector<HTMLElement>(`[data-calendar-${name}]`);\n          if (!outgoing || !incoming) return;\n          outgoing.textContent = oldText;\n          gsap.fromTo(outgoing,\n            { yPercent: 0, opacity: 1 },\n            { yPercent: -direction * 100, opacity: 0, duration, ease: \"power3.out\" },\n          );\n          gsap.fromTo(incoming,\n            { yPercent: direction * 100, opacity: 0 },\n            { yPercent: 0, opacity: 1, duration, ease: \"power3.out\" },\n          );\n        });\n      }\n    }, rootRef);\n    return () => media.revert();\n  }, { scope: rootRef, dependencies: [day, pickerYear, ready], revertOnUpdate: true });\n\n  useGSAP(() => {\n    const grid = rootRef.current?.querySelector<HTMLElement>(\"[data-calendar-grid]\");\n    const hover = grid?.querySelector<HTMLElement>(\"[data-calendar-hover]\");\n    if (!grid || !hover) return;\n    const media = gsap.matchMedia();\n    media.add({ reduced: \"(prefers-reduced-motion: reduce)\", standard: \"(prefers-reduced-motion: no-preference)\" }, (context) => {\n      const duration = context.conditions?.reduced ? 0 : 0.24;\n      const xTo = gsap.quickTo(hover, \"x\", { duration, ease: \"power3.out\" });\n      const yTo = gsap.quickTo(hover, \"y\", { duration, ease: \"power3.out\" });\n      const opacityTo = gsap.quickTo(hover, \"opacity\", { duration: duration * 0.75 });\n      let hovered: HTMLElement | null = null;\n      const hide = () => { hovered = null; opacityTo(0); };\n      const move = (event: PointerEvent) => {\n        if (event.pointerType === \"touch\") return;\n        const target = (event.target as HTMLElement).closest<HTMLElement>(\"[data-calendar-date]\");\n        if (!target || !grid.contains(target)) return;\n        const first = !hovered;\n        hovered = target;\n        xTo(target.offsetLeft);\n        yTo(target.offsetTop);\n        if (first) {\n          xTo.tween.progress(1);\n          yTo.tween.progress(1);\n        }\n        opacityTo(1);\n      };\n      let width = grid.clientWidth;\n      const observer = new ResizeObserver(() => {\n        if (grid.clientWidth === width) return;\n        width = grid.clientWidth;\n        hide();\n      });\n      observer.observe(grid);\n      grid.addEventListener(\"pointerover\", move);\n      grid.addEventListener(\"pointerleave\", hide);\n      return () => {\n        observer.disconnect();\n        grid.removeEventListener(\"pointerover\", move);\n        grid.removeEventListener(\"pointerleave\", hide);\n      };\n    }, rootRef);\n    return () => media.revert();\n  }, { scope: rootRef, dependencies: [year, month, pickerYear !== null, ready, density, weekStartsOn], revertOnUpdate: true });\n\n  useEffect(() => {\n    if (!ready || !rootRef.current) return;\n    const reduced = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n    const fills = rootRef.current.querySelectorAll(\"[data-calendar-fill]\");\n    const fillTween = fills.length ? gsap.to(fills, {\n      scaleX: (index: number) => Math.max(0, Math.min(1, day / count * 4 - index)),\n      duration: reduced ? 0 : 0.35,\n      ease: \"power3.out\",\n      overwrite: true,\n    }) : undefined;\n    const highlight = rootRef.current.querySelector<HTMLElement>(\"[data-calendar-highlight]\");\n    const selected = rootRef.current.querySelector<HTMLElement>(\"[data-selected-day]\");\n    let highlightTween: gsap.core.Tween | undefined;\n    let observer: ResizeObserver | undefined;\n    if (highlight && selected) {\n      const first = previousHighlight.current !== highlight;\n      previousHighlight.current = highlight;\n      const position = { x: selected.offsetLeft, y: selected.offsetTop, opacity: 1 };\n      highlightTween = gsap.to(highlight, {\n        ...position,\n        duration: reduced || first ? 0 : 0.3,\n        ease: \"power3.out\",\n        overwrite: true,\n      });\n      let width = highlight.parentElement!.clientWidth;\n      observer = new ResizeObserver(() => {\n        const nextWidth = highlight.parentElement!.clientWidth;\n        if (nextWidth === width) return;\n        width = nextWidth;\n        highlightTween?.kill();\n        gsap.set(highlight, { x: selected.offsetLeft, y: selected.offsetTop, opacity: 1 });\n      });\n      observer.observe(highlight.parentElement!);\n    }\n    return () => {\n      fillTween?.kill();\n      highlightTween?.kill();\n      observer?.disconnect();\n    };\n  }, [day, count, year, month, pickerYear, ready, density, weekStartsOn, showProgress]);\n\n  useEffect(() => {\n    if (!keyboardFocus.current) return;\n    rootRef.current?.querySelector<HTMLButtonElement>(\"[data-selected-day]\")?.focus();\n    keyboardFocus.current = false;\n  }, [year, month, day]);\n\n  useGSAP(() => {\n    const popup = popupRef.current;\n    const anchor = popupAnchor.current;\n    const root = rootRef.current;\n    if (!popupOpen || !popup || !anchor || !root) return;\n    const media = gsap.matchMedia();\n    const position = () => {\n      const card = root.getBoundingClientRect();\n      const cell = anchor.getBoundingClientRect();\n      const width = popup.offsetWidth;\n      const height = popup.offsetHeight;\n      const x = Math.max(12, Math.min(cell.left - card.left + cell.width / 2 - width / 2, root.clientWidth - width - 12));\n      const below = cell.bottom - card.top + 6;\n      const y = Math.max(12, below + height < root.clientHeight - 12 ? below : cell.top - card.top - height - 6);\n      gsap.set(popup, { left: x, top: y });\n    };\n    position();\n    const observer = new ResizeObserver(position);\n    observer.observe(root);\n    observer.observe(popup);\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n      gsap.from(popup, { y: 5, opacity: 0, duration: 0.18, ease: \"power3.out\", clearProps: \"transform,opacity\" });\n    }, rootRef);\n    return () => { observer.disconnect(); media.revert(); };\n  }, { scope: rootRef, dependencies: [popupDay, popupOpen, year, month, density, weekStartsOn], revertOnUpdate: true });\n\n  useEffect(() => () => { closePopupTween.current?.kill(); }, []);\n\n  useEffect(() => {\n    if (!popupOpen) return;\n    const dismiss = (event: PointerEvent) => {\n      const target = event.target as Node;\n      if (!popupRef.current?.contains(target) && !popupAnchor.current?.contains(target)) setPopupDay(null);\n    };\n    document.addEventListener(\"pointerdown\", dismiss, true);\n    return () => document.removeEventListener(\"pointerdown\", dismiss, true);\n  }, [popupOpen]);\n\n  function showPopup(number: number, anchor: HTMLButtonElement) {\n    closePopupTween.current?.kill();\n    popupAnchor.current = anchor;\n    setPopupDay(number);\n  }\n\n  function schedulePopupClose() {\n    closePopupTween.current?.kill();\n    closePopupTween.current = gsap.delayedCall(0.15, () => {\n      if (!popupRef.current?.contains(document.activeElement)) setPopupDay(null);\n    });\n  }\n\n  function pick(next: Date) {\n    closePopupTween.current?.kill();\n    setPopupDay(null);\n    if (value === undefined) setInternalDate(next);\n    onChange?.(new Date(next));\n  }\n\n  function onDayKey(event: KeyboardEvent<HTMLButtonElement>, number: number) {\n    const current = new Date(year, month, number);\n    const steps: Record<string, number> = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };\n    let next: Date;\n    if (event.key in steps) {\n      next = new Date(year, month, number + steps[event.key]);\n    } else if (event.key === \"Home\") next = new Date(year, month, 1);\n    else if (event.key === \"End\") next = new Date(year, month, count);\n    else if (event.key === \"PageUp\" || event.key === \"PageDown\") {\n      next = inMonth(current, year, month + (event.key === \"PageUp\" ? -1 : 1));\n    } else return;\n    event.preventDefault();\n    keyboardFocus.current = true;\n    pick(next);\n  }\n\n  return (\n    <Dialog.Root open={eventFormDate !== null} onOpenChange={(open) => { setEventFormDate(open ? dateKey : null); setPopupDay(null); }}>\n    <div\n      ref={rootRef}\n      data-slot=\"calendar-card\"\n      role=\"group\"\n      aria-label=\"Date picker\"\n      aria-busy={!ready}\n      onKeyDown={(event) => { if (event.key === \"Escape\" && popupOpen) { event.stopPropagation(); popupAnchor.current?.focus(); setPopupDay(null); } }}\n      style={{\n        \"--calendar-accent\": accentColor,\n        \"--calendar-cell-mobile\": density === \"compact\" ? \"36px\" : \"40px\",\n        \"--calendar-cell-desktop\": density === \"compact\" ? \"36px\" : \"44px\",\n      } as CSSProperties}\n      className={cn(\n        \"@container relative w-full max-w-[44rem] rounded-3xl border border-white/10 bg-[#191919] p-4 text-zinc-100 shadow-[0_24px_48px_-16px_#000,0_1px_1px_0_#ffffff18,inset_0_1px_0_0_#ffffff0a] sm:rounded-[2rem] sm:p-8\",\n        className,\n      )}\n    >\n      <div className={cn(\"@min-[480px]:grid @min-[480px]:grid-cols-[184px_minmax(0,1fr)] @min-[480px]:gap-x-8\", !ready && \"invisible\")}>\n        <div className=\"@min-[480px]:col-start-1 @min-[480px]:row-start-1\">\n          <div className={cn(\"flex items-start justify-between gap-3 @min-[480px]:flex-col-reverse @min-[480px]:gap-5\", onEventAdd && \"flex-col-reverse gap-4\")}>\n            <div className=\"flex min-w-0 items-center gap-4 @min-[480px]:block\">\n              <span className=\"mb-3 hidden text-[10px] font-medium uppercase tracking-[0.18em] text-zinc-400 @min-[480px]:block\">Selected date</span>\n              <div data-calendar-day-number className=\"relative h-14 w-[2ch] shrink-0 overflow-hidden text-5xl font-medium leading-none tracking-[-0.07em] tabular-nums @min-[480px]:h-20 @min-[480px]:text-7xl\" aria-hidden=\"true\">\n                <span data-calendar-day-out className=\"absolute inset-x-0 top-0 opacity-0\" />\n                <span data-calendar-day className=\"block\">{day}</span>\n              </div>\n              <button ref={titleRef} type=\"button\" aria-expanded={pickerYear !== null} aria-controls={panelId} aria-label=\"Choose month and year\" onClick={() => setPickerYear(pickerYear === null ? year : null)} className={cn(buttonClass, \"grid min-h-11 flex-1 grid-cols-[1fr_auto] items-center gap-x-2 px-2 py-1 text-left text-base hover:bg-white/5 @min-[480px]:-ml-2 @min-[480px]:flex @min-[480px]:flex-none @min-[480px]:min-h-0 @min-[480px]:gap-1\")}>\n                <span className=\"col-start-1 font-medium @min-[480px]:w-24 @min-[480px]:shrink-0\">{months[month]}</span>\n                <span className=\"col-start-1 row-start-2 text-sm text-zinc-400 @min-[480px]:w-[4ch] @min-[480px]:shrink-0 @min-[480px]:text-base @min-[480px]:tabular-nums\">{year}</span>\n                <ChevronDown aria-hidden=\"true\" size={14} className={cn(\"col-start-2 row-span-2 row-start-1 text-zinc-400\", pickerYear !== null && \"rotate-180\")} />\n              </button>\n            </div>\n            <div className={cn(\"mt-1 flex items-center gap-3\", onEventAdd && \"w-full justify-between\")}>\n              <button type=\"button\" aria-label=\"Go to today\" title=\"Go to today\" onClick={() => { const now = new Date(); pick(new Date(now.getFullYear(), now.getMonth(), now.getDate())); setPickerYear(null); }} className={cn(buttonClass, \"group h-10 w-10 shrink-0 @min-[480px]:h-12 @min-[480px]:w-12 overflow-hidden border border-white/5 bg-[#202020] shadow-[0_3px_8px_#0004] hover:bg-[#292929]\")}>\n                <span aria-hidden=\"true\" className=\"block h-3 bg-[var(--calendar-accent)] shadow-[0_2px_8px_color-mix(in_srgb,var(--calendar-accent)_20%,transparent)]\" />\n                <span aria-hidden=\"true\" className=\"grid grid-cols-7 gap-0.5 p-2\">\n                  {Array.from({ length: 21 }, (_, index) => <span key={index} className={cn(\"h-0.5 rounded-full bg-zinc-600\", index === (day - 1) % 21 && \"bg-[var(--calendar-accent)]\")} />)}\n                </span>\n              </button>\n              {onEventAdd && (\n                <Dialog.Trigger asChild>\n                  <button type=\"button\" className={cn(buttonClass, \"flex min-h-10 shrink-0 items-center gap-2 border border-white/10 bg-white/5 px-3 py-2 text-xs font-medium hover:bg-white/10\")}><Plus size={14} aria-hidden=\"true\" />Add event</button>\n                </Dialog.Trigger>\n              )}\n            </div>\n          </div>\n\n          {showProgress && <div className=\"my-4 flex gap-1.5 @min-[480px]:my-6\" aria-hidden=\"true\">\n            {Array.from({ length: 4 }, (_, index) => (\n              <span key={index} className=\"h-1 flex-1 overflow-hidden rounded-full bg-white/7\">\n                <span data-calendar-fill className=\"block h-full origin-left rounded-full bg-[var(--calendar-accent)]\" style={{ transform: \"scaleX(0)\" }} />\n              </span>\n            ))}\n          </div>}\n        </div>\n\n        <div id={panelId} data-calendar-panel className=\"min-h-72 @min-[480px]:col-start-2 @min-[480px]:row-span-2 @min-[480px]:row-start-1 @min-[480px]:min-h-86 @min-[480px]:border-l @min-[480px]:border-white/5 @min-[480px]:pl-8\" onKeyDown={(event) => { if (event.key === \"Escape\" && pickerYear !== null) { setPickerYear(null); titleRef.current?.focus(); } }}>\n          {pickerYear !== null ? (\n            <div>\n              <div className=\"mb-5 flex items-center justify-between\">\n                <button type=\"button\" aria-label=\"Previous year\" onClick={() => setPickerYear(pickerYear - 1)} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center rounded-full bg-white/5 @min-[480px]:h-9 @min-[480px]:w-9 hover:bg-white/10\")}><ChevronLeft aria-hidden=\"true\" size={16} /></button>\n                <span aria-live=\"polite\" className=\"inline-flex h-7 items-center justify-center text-lg font-semibold leading-7 tabular-nums\">\n                  <span className=\"sr-only\">{pickerYear}</span>\n                  {String(pickerYear).split(\"\").map((digit, index, digits) => (\n                    <span key={digits.length - index} data-calendar-year-digit={digits.length - index - 1} aria-hidden=\"true\" className=\"relative block h-7 w-[1ch] overflow-hidden text-center\">\n                      <span data-calendar-year-out className=\"absolute inset-x-0 top-0 opacity-0\" />\n                      <span data-calendar-year className=\"block\">{digit}</span>\n                    </span>\n                  ))}\n                </span>\n                <button type=\"button\" aria-label=\"Next year\" onClick={() => setPickerYear(pickerYear + 1)} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center rounded-full bg-white/5 @min-[480px]:h-9 @min-[480px]:w-9 hover:bg-white/10\")}><ChevronRight aria-hidden=\"true\" size={16} /></button>\n              </div>\n              <div role=\"group\" aria-label={`Months in ${pickerYear}`} className=\"grid grid-cols-3 gap-x-4 gap-y-3 @min-[480px]:gap-y-5\">\n                {months.map((name, index) => (\n                  <button key={name} type=\"button\" aria-label={`${name} ${pickerYear}`} aria-pressed={index === month && pickerYear === year} onClick={() => { pick(inMonth(date, pickerYear, index)); setPickerYear(null); titleRef.current?.focus(); }} className={cn(buttonClass, \"h-11 text-sm font-medium @min-[480px]:h-11 @min-[480px]:text-base\", index === month && pickerYear === year ? \"bg-[var(--calendar-accent)] text-zinc-950 shadow-[0_2px_12px_color-mix(in_srgb,var(--calendar-accent)_16%,transparent)]\" : \"text-zinc-300 hover:bg-white/5 hover:text-white\")}>\n                    {name.slice(0, 3)}\n                  </button>\n                ))}\n              </div>\n            </div>\n          ) : (\n            <div role=\"group\" aria-label={`${months[month]} ${year}`}>\n              <div aria-hidden=\"true\" className=\"mb-3 grid grid-cols-7 @min-[480px]:mb-5\">\n                {week.map((name) => <span key={name} className=\"text-center text-xs font-medium text-zinc-400\">{name[0]}</span>)}\n              </div>\n              <div data-calendar-grid className=\"relative grid grid-cols-7 gap-y-1 @min-[480px]:gap-y-2\">\n                <span data-calendar-highlight aria-hidden=\"true\" className=\"pointer-events-none absolute left-0 top-0 h-[var(--calendar-cell-mobile)] w-[calc(100%/7)] rounded-lg bg-[var(--calendar-accent)] opacity-0 shadow-[0_2px_12px_color-mix(in_srgb,var(--calendar-accent)_16%,transparent)] @min-[480px]:h-[var(--calendar-cell-desktop)]\" />\n                <span data-calendar-hover aria-hidden=\"true\" className=\"pointer-events-none absolute left-0 top-0 z-[1] h-[var(--calendar-cell-mobile)] w-[calc(100%/7)] rounded-lg border border-white/10 bg-white/5 opacity-0 @min-[480px]:h-[var(--calendar-cell-desktop)]\" />\n                {Array.from({ length: offset }, (_, index) => <span key={`empty-${index}`} aria-hidden=\"true\" />)}\n                {Array.from({ length: count }, (_, index) => {\n                  const number = index + 1;\n                  const dayEvents = eventsByDay.get(number) ?? [];\n                  return (\n                    <button\n                      key={number}\n                      type=\"button\"\n                      data-calendar-date=\"\"\n                      data-selected-day={number === day ? \"\" : undefined}\n                      aria-label={`${months[month]} ${number}, ${year}${dayEvents.length ? `, ${dayEvents.length} ${dayEvents.length === 1 ? \"event\" : \"events\"}` : \"\"}`}\n                      aria-pressed={number === day}\n                      tabIndex={number === day ? 0 : -1}\n                      aria-haspopup={showEvents && dayEvents.length > 0 ? \"dialog\" : undefined}\n                      aria-expanded={showEvents && dayEvents.length > 0 ? popupOpen && popupDay === number : undefined}\n                      aria-controls={popupOpen && popupDay === number ? popupId : undefined}\n                      onPointerEnter={(event) => { if (event.pointerType !== \"touch\" && !eventFormDate) showPopup(number, event.currentTarget); }}\n                      onPointerLeave={schedulePopupClose}\n                      onFocus={(event) => { if (event.currentTarget.matches(\":focus-visible\") && !eventFormDate) showPopup(number, event.currentTarget); }}\n                      onBlur={(event) => { if (!popupRef.current?.contains(event.relatedTarget)) schedulePopupClose(); }}\n                      onClick={(event) => { pick(new Date(year, month, number)); showPopup(number, event.currentTarget); }}\n                      onKeyDown={(event) => onDayKey(event, number)}\n                      className={cn(\n                        buttonClass,\n                        \"relative z-10 flex h-[var(--calendar-cell-mobile)] flex-col items-center justify-center rounded-lg text-base font-medium tabular-nums @min-[480px]:h-[var(--calendar-cell-desktop)]\",\n                        number === day\n                          ? \"text-zinc-950\"\n                          : \"text-zinc-200 hover:text-white\",\n                      )}\n                    >\n                      <span className=\"leading-none\">{number}</span>\n                      {events.length > 0 && (\n                        <span aria-hidden=\"true\" className=\"mt-1 flex h-[3px] items-center justify-center gap-[3px]\">\n                          {dayEvents.slice(0, 3).map((event) => <span key={event.id} className=\"size-[3px] rounded-full\" style={{ backgroundColor: number === day ? \"#09090b\" : event.color ?? accentColor }} />)}\n                        </span>\n                      )}\n                    </button>\n                  );\n                })}\n              </div>\n            </div>\n          )}\n        </div>\n        {popupOpen && (\n          <div ref={popupRef} id={popupId} role=\"dialog\" aria-label={`Events for ${months[month]} ${popupDay}, ${year}`} onPointerEnter={() => closePopupTween.current?.kill()} onPointerLeave={schedulePopupClose} onFocus={() => closePopupTween.current?.kill()} onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget)) schedulePopupClose(); }} className=\"absolute z-30 w-[min(17rem,calc(100%_-_24px))] rounded-2xl border border-white/10 bg-[#242424] p-4 shadow-[0_12px_40px_#0009]\">\n            <div className=\"mb-3 flex items-center justify-between gap-3\">\n              <span className=\"text-xs font-medium\">{months[month].slice(0, 3)} {popupDay} · {popupEvents.length} {popupEvents.length === 1 ? \"event\" : \"events\"}</span>\n              <button type=\"button\" aria-label=\"Close event popup\" onClick={() => { popupAnchor.current?.focus(); setPopupDay(null); }} className={cn(buttonClass, \"flex size-6 items-center justify-center text-zinc-400 hover:bg-white/10\")}><X size={13} aria-hidden=\"true\" /></button>\n            </div>\n            <ul className=\"max-h-40 space-y-2 overflow-y-auto\">\n              {popupEvents.map((event) => (\n                <li key={event.id}>\n                  <EventTag type={onEventClick ? \"button\" : undefined} onClick={onEventClick ? () => onEventClick(event) : undefined} className={cn(\"flex w-full items-start gap-2 rounded-xl px-2 py-2 text-left\", onEventClick && [buttonClass, \"bg-white/4 hover:bg-white/8\"])}>\n                    <span aria-hidden=\"true\" className=\"mt-1 size-1.5 shrink-0 rounded-full\" style={{ backgroundColor: event.color ?? accentColor }} />\n                    <span className=\"min-w-0 flex-1\">\n                      <span className=\"block break-words text-xs font-medium\">{event.title}</span>\n                      <span className=\"mt-1 block text-[10px] text-zinc-400\">{event.time ?? \"All day\"}{event.assignee && ` · ${event.assignee}`}</span>\n                    </span>\n                  </EventTag>\n                </li>\n              ))}\n            </ul>\n            {onEventAdd && <button type=\"button\" onClick={() => { setEventFormDate(`${monthKey}-${String(popupDay).padStart(2, \"0\")}`); setPopupDay(null); }} className={cn(buttonClass, \"mt-3 flex w-full items-center justify-center gap-2 border-t border-white/10 pt-3 text-xs text-zinc-300\")}><Plus size={13} aria-hidden=\"true\" />Add event to this day</button>}\n          </div>\n        )}\n        <div className=\"mt-4 flex items-center justify-between border-t border-white/5 pt-3 @min-[480px]:col-start-1 @min-[480px]:row-start-2 @min-[480px]:self-end\">\n          <button type=\"button\" aria-label=\"Previous month\" onClick={() => { pick(inMonth(date, year, month - 1)); setPickerYear(null); }} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center text-zinc-400 @min-[480px]:h-8 @min-[480px]:w-8 hover:bg-white/5 hover:text-white\")}><ChevronLeft aria-hidden=\"true\" size={15} /></button>\n          <span className=\"flex-1 text-center text-xs font-medium text-zinc-400\">{weekdays[(date.getDay() + 6) % 7].slice(0, 3)}, {months[month].slice(0, 3)} {day}</span>\n          <button type=\"button\" aria-label=\"Advance one month\" onClick={() => { pick(inMonth(date, year, month + 1)); setPickerYear(null); }} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center text-zinc-400 @min-[480px]:h-8 @min-[480px]:w-8 hover:bg-white/5 hover:text-white\")}><ChevronRight aria-hidden=\"true\" size={15} /></button>\n        </div>\n        <span role=\"status\" className=\"sr-only\">Selected date: {selectedLabel}{events.length > 0 && `, ${selectedEvents.length} ${selectedEvents.length === 1 ? \"event\" : \"events\"}`}</span>\n      </div>\n    </div>\n    <Dialog.Portal>\n      <Dialog.Overlay className=\"fixed inset-0 z-50 bg-black/60 backdrop-blur-sm\" />\n      <Dialog.Content onOpenAutoFocus={(event) => { event.preventDefault(); eventTitleRef.current?.focus(); }} style={{ \"--calendar-accent\": accentColor } as CSSProperties} className=\"fixed left-1/2 top-1/2 z-50 max-h-[90dvh] w-[calc(100%_-_2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-3xl border border-white/10 bg-[#202020] p-6 text-zinc-100 shadow-2xl\">\n        <Dialog.Title className=\"text-lg font-semibold\">Add an event</Dialog.Title>\n        <Dialog.Description className=\"mt-1 text-xs text-zinc-400\">Choose a date and optionally assign it to someone.</Dialog.Description>\n        <Dialog.Close aria-label=\"Close add event\" className={cn(buttonClass, \"absolute right-4 top-4 flex size-8 items-center justify-center hover:bg-white/5\")}><X size={16} aria-hidden=\"true\" /></Dialog.Close>\n        <form className=\"mt-5 space-y-4\" onSubmit={(event) => {\n          event.preventDefault();\n          const form = event.currentTarget;\n          const fields = new FormData(form);\n          const title = String(fields.get(\"title\") ?? \"\").trim();\n          const titleInput = form.elements.namedItem(\"title\") as HTMLInputElement;\n          if (!title) { titleInput.setCustomValidity(\"Enter an event title.\"); titleInput.reportValidity(); return; }\n          const eventDate = String(fields.get(\"date\"));\n          onEventAdd?.({ id: `${panelId}-event-${++eventCounter.current}`, date: eventDate, title, time: String(fields.get(\"time\") ?? \"\") || undefined, assignee: String(fields.get(\"assignee\") ?? \"\").trim() || undefined, color: String(fields.get(\"color\")) });\n          pick(new Date(`${eventDate}T00:00:00`));\n          setEventFormDate(null);\n        }}>\n          <label className=\"block space-y-1.5 text-xs text-zinc-300\">Title<input ref={eventTitleRef} name=\"title\" required maxLength={120} placeholder=\"Design review\" onInput={(event) => event.currentTarget.setCustomValidity(\"\")} className={cn(buttonClass, \"block w-full cursor-text rounded-lg border border-white/10 bg-white/5 px-3 py-2.5 text-base text-white sm:text-sm\")} /></label>\n          <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n            <label className=\"block space-y-1.5 text-xs text-zinc-300\">Date<input name=\"date\" type=\"date\" required defaultValue={eventFormDate ?? dateKey} className={cn(buttonClass, \"block w-full min-w-0 rounded-lg border border-white/10 bg-white/5 px-2 py-2.5 text-base [color-scheme:dark] sm:text-xs\")} /></label>\n            <label className=\"block space-y-1.5 text-xs text-zinc-300\">Time<input name=\"time\" type=\"time\" className={cn(buttonClass, \"block w-full min-w-0 rounded-lg border border-white/10 bg-white/5 px-2 py-2.5 text-base [color-scheme:dark] sm:text-xs\")} /></label>\n          </div>\n          <label className=\"block space-y-1.5 text-xs text-zinc-300\">Assign to <span className=\"text-zinc-500\">(optional)</span><input name=\"assignee\" maxLength={80} placeholder=\"Name or team\" className={cn(buttonClass, \"block w-full cursor-text rounded-lg border border-white/10 bg-white/5 px-3 py-2.5 text-base text-white sm:text-sm\")} /></label>\n          <label className=\"flex items-center justify-between text-xs text-zinc-300\">Event color<input name=\"color\" type=\"color\" defaultValue={accentColor.startsWith(\"#\") && accentColor.length === 7 ? accentColor : \"#ff675c\"} className=\"h-8 w-10 cursor-pointer rounded border-0 bg-transparent\" /></label>\n          <div className=\"flex justify-end gap-2 pt-2\">\n            <Dialog.Close className={cn(buttonClass, \"px-4 py-2 text-xs text-zinc-400 hover:bg-white/5\")}>Cancel</Dialog.Close>\n            <button type=\"submit\" className={cn(buttonClass, \"bg-[var(--calendar-accent)] px-4 py-2 text-xs font-semibold text-zinc-950\")}>Save event</button>\n          </div>\n        </form>\n      </Dialog.Content>\n    </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nexport default CalendarCard;\n",
      "content": "\"use client\";\n\nimport {\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type KeyboardEvent,\n  type CSSProperties,\n} from \"react\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { ChevronDown, ChevronLeft, ChevronRight, Plus, X } from \"lucide-react\";\nimport * as Dialog from \"@radix-ui/react-dialog\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport interface CalendarEvent {\n  id: string;\n  date: string;\n  title: string;\n  time?: string;\n  color?: string;\n  assignee?: string;\n}\n\nexport interface CalendarCardProps {\n  value?: Date;\n  defaultValue?: Date;\n  onChange?: (date: Date) => void;\n  className?: string;\n  events?: CalendarEvent[];\n  onEventClick?: (event: CalendarEvent) => void;\n  onEventAdd?: (event: CalendarEvent) => void;\n  accentColor?: string;\n  density?: \"comfortable\" | \"compact\";\n  weekStartsOn?: 0 | 1;\n  showEvents?: boolean;\n  showProgress?: boolean;\n}\n\nconst months = [\n  \"January\", \"February\", \"March\", \"April\", \"May\", \"June\",\n  \"July\", \"August\", \"September\", \"October\", \"November\", \"December\",\n];\nconst weekdays = [\n  \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\",\n];\nconst subscribe = () => () => {};\nconst readToday = () => new Date().setHours(0, 0, 0, 0);\nconst serverToday = () => 0;\n\nfunction inMonth(date: Date, year: number, month: number) {\n  return new Date(\n    year,\n    month,\n    Math.min(date.getDate(), new Date(year, month + 1, 0).getDate()),\n  );\n}\n\nexport function CalendarCard({\n  value, defaultValue, onChange, className, events = [], onEventClick, onEventAdd,\n  accentColor = \"#ff675c\", density = \"comfortable\", weekStartsOn = 1,\n  showEvents = true, showProgress = true,\n}: CalendarCardProps) {\n  const today = useSyncExternalStore(subscribe, readToday, serverToday);\n  const [internalDate, setInternalDate] = useState(defaultValue);\n  const date = value ?? internalDate ?? new Date(today);\n  const ready = Boolean(value || internalDate || today);\n  const year = date.getFullYear();\n  const month = date.getMonth();\n  const day = date.getDate();\n  const [pickerYear, setPickerYear] = useState<number | null>(null);\n  const rootRef = useRef<HTMLDivElement>(null);\n  const popupRef = useRef<HTMLDivElement>(null);\n  const eventTitleRef = useRef<HTMLInputElement>(null);\n  const eventCounter = useRef(0);\n  const popupAnchor = useRef<HTMLButtonElement | null>(null);\n  const closePopupTween = useRef<gsap.core.Tween | null>(null);\n  const [popupDay, setPopupDay] = useState<number | null>(null);\n  const [eventFormDate, setEventFormDate] = useState<string | null>(null);\n  const titleRef = useRef<HTMLButtonElement>(null);\n  const keyboardFocus = useRef(false);\n  const previousNumbers = useRef({ day, year: pickerYear });\n  const previousHighlight = useRef<HTMLElement | null>(null);\n  const panelId = useId();\n  const popupId = useId();\n  const count = new Date(year, month + 1, 0).getDate();\n  const offset = (new Date(year, month, 1).getDay() - weekStartsOn + 7) % 7;\n  const week = weekStartsOn === 1 ? weekdays : [weekdays[6], ...weekdays.slice(0, 6)];\n  const monthKey = `${year}-${String(month + 1).padStart(2, \"0\")}`;\n  const eventsByDay = new Map<number, CalendarEvent[]>();\n  for (const event of events) {\n    if (!event.date.startsWith(`${monthKey}-`)) continue;\n    const eventDay = Number(event.date.slice(monthKey.length + 1));\n    if (event.date !== `${monthKey}-${String(eventDay).padStart(2, \"0\")}` || eventDay < 1 || eventDay > count) continue;\n    const entries = eventsByDay.get(eventDay) ?? [];\n    entries.push(event);\n    eventsByDay.set(eventDay, entries);\n  }\n  const selectedEvents = eventsByDay.get(day) ?? [];\n  const popupEvents = eventsByDay.get(popupDay ?? 0) ?? [];\n  const popupOpen = showEvents && pickerYear === null && popupDay !== null && popupEvents.length > 0;\n  const dateKey = `${monthKey}-${String(day).padStart(2, \"0\")}`;\n  const EventTag = onEventClick ? \"button\" : \"div\";\n  const selectedLabel = `${months[month]} ${day}, ${year}`;\n  const buttonClass = \"cursor-pointer rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-[var(--calendar-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[#191919]\";\n\n  useGSAP(\n    () => {\n      if (!ready) return;\n      const media = gsap.matchMedia();\n      media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n        gsap.from(\"[data-calendar-panel]\", {\n          y: 8, opacity: 0, duration: 0.28,\n          ease: \"power3.out\", clearProps: \"transform,opacity\",\n        });\n      }, rootRef);\n      return () => media.revert();\n    },\n    {\n      scope: rootRef,\n      dependencies: [year, month, pickerYear !== null, ready],\n      revertOnUpdate: true,\n    },\n  );\n\n  useGSAP(() => {\n    const previous = previousNumbers.current;\n    previousNumbers.current = { day, year: pickerYear };\n    if (!ready) return;\n    const media = gsap.matchMedia();\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n      for (const [name, before, after] of [\n        [\"day\", previous.day, day],\n        [\"year\", previous.year, pickerYear],\n      ] as const) {\n        if (before === null || after === null || before === after) continue;\n        const direction = after > before ? 1 : -1;\n        const duration = name === \"year\" ? 0.38 : 0.3;\n        const slots = rootRef.current?.querySelectorAll<HTMLElement>(\n          name === \"year\" ? \"[data-calendar-year-digit]\" : \"[data-calendar-day-number]\",\n        );\n        slots?.forEach((slot) => {\n          const place = Number(slot.dataset.calendarYearDigit ?? 0);\n          const oldText = name === \"year\" ? String(before).at(-place - 1) ?? \"\" : String(before);\n          const newText = name === \"year\" ? String(after).at(-place - 1) ?? \"\" : String(after);\n          if (oldText === newText) return;\n          const outgoing = slot.querySelector<HTMLElement>(`[data-calendar-${name}-out]`);\n          const incoming = slot.querySelector<HTMLElement>(`[data-calendar-${name}]`);\n          if (!outgoing || !incoming) return;\n          outgoing.textContent = oldText;\n          gsap.fromTo(outgoing,\n            { yPercent: 0, opacity: 1 },\n            { yPercent: -direction * 100, opacity: 0, duration, ease: \"power3.out\" },\n          );\n          gsap.fromTo(incoming,\n            { yPercent: direction * 100, opacity: 0 },\n            { yPercent: 0, opacity: 1, duration, ease: \"power3.out\" },\n          );\n        });\n      }\n    }, rootRef);\n    return () => media.revert();\n  }, { scope: rootRef, dependencies: [day, pickerYear, ready], revertOnUpdate: true });\n\n  useGSAP(() => {\n    const grid = rootRef.current?.querySelector<HTMLElement>(\"[data-calendar-grid]\");\n    const hover = grid?.querySelector<HTMLElement>(\"[data-calendar-hover]\");\n    if (!grid || !hover) return;\n    const media = gsap.matchMedia();\n    media.add({ reduced: \"(prefers-reduced-motion: reduce)\", standard: \"(prefers-reduced-motion: no-preference)\" }, (context) => {\n      const duration = context.conditions?.reduced ? 0 : 0.24;\n      const xTo = gsap.quickTo(hover, \"x\", { duration, ease: \"power3.out\" });\n      const yTo = gsap.quickTo(hover, \"y\", { duration, ease: \"power3.out\" });\n      const opacityTo = gsap.quickTo(hover, \"opacity\", { duration: duration * 0.75 });\n      let hovered: HTMLElement | null = null;\n      const hide = () => { hovered = null; opacityTo(0); };\n      const move = (event: PointerEvent) => {\n        if (event.pointerType === \"touch\") return;\n        const target = (event.target as HTMLElement).closest<HTMLElement>(\"[data-calendar-date]\");\n        if (!target || !grid.contains(target)) return;\n        const first = !hovered;\n        hovered = target;\n        xTo(target.offsetLeft);\n        yTo(target.offsetTop);\n        if (first) {\n          xTo.tween.progress(1);\n          yTo.tween.progress(1);\n        }\n        opacityTo(1);\n      };\n      let width = grid.clientWidth;\n      const observer = new ResizeObserver(() => {\n        if (grid.clientWidth === width) return;\n        width = grid.clientWidth;\n        hide();\n      });\n      observer.observe(grid);\n      grid.addEventListener(\"pointerover\", move);\n      grid.addEventListener(\"pointerleave\", hide);\n      return () => {\n        observer.disconnect();\n        grid.removeEventListener(\"pointerover\", move);\n        grid.removeEventListener(\"pointerleave\", hide);\n      };\n    }, rootRef);\n    return () => media.revert();\n  }, { scope: rootRef, dependencies: [year, month, pickerYear !== null, ready, density, weekStartsOn], revertOnUpdate: true });\n\n  useEffect(() => {\n    if (!ready || !rootRef.current) return;\n    const reduced = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n    const fills = rootRef.current.querySelectorAll(\"[data-calendar-fill]\");\n    const fillTween = fills.length ? gsap.to(fills, {\n      scaleX: (index: number) => Math.max(0, Math.min(1, day / count * 4 - index)),\n      duration: reduced ? 0 : 0.35,\n      ease: \"power3.out\",\n      overwrite: true,\n    }) : undefined;\n    const highlight = rootRef.current.querySelector<HTMLElement>(\"[data-calendar-highlight]\");\n    const selected = rootRef.current.querySelector<HTMLElement>(\"[data-selected-day]\");\n    let highlightTween: gsap.core.Tween | undefined;\n    let observer: ResizeObserver | undefined;\n    if (highlight && selected) {\n      const first = previousHighlight.current !== highlight;\n      previousHighlight.current = highlight;\n      const position = { x: selected.offsetLeft, y: selected.offsetTop, opacity: 1 };\n      highlightTween = gsap.to(highlight, {\n        ...position,\n        duration: reduced || first ? 0 : 0.3,\n        ease: \"power3.out\",\n        overwrite: true,\n      });\n      let width = highlight.parentElement!.clientWidth;\n      observer = new ResizeObserver(() => {\n        const nextWidth = highlight.parentElement!.clientWidth;\n        if (nextWidth === width) return;\n        width = nextWidth;\n        highlightTween?.kill();\n        gsap.set(highlight, { x: selected.offsetLeft, y: selected.offsetTop, opacity: 1 });\n      });\n      observer.observe(highlight.parentElement!);\n    }\n    return () => {\n      fillTween?.kill();\n      highlightTween?.kill();\n      observer?.disconnect();\n    };\n  }, [day, count, year, month, pickerYear, ready, density, weekStartsOn, showProgress]);\n\n  useEffect(() => {\n    if (!keyboardFocus.current) return;\n    rootRef.current?.querySelector<HTMLButtonElement>(\"[data-selected-day]\")?.focus();\n    keyboardFocus.current = false;\n  }, [year, month, day]);\n\n  useGSAP(() => {\n    const popup = popupRef.current;\n    const anchor = popupAnchor.current;\n    const root = rootRef.current;\n    if (!popupOpen || !popup || !anchor || !root) return;\n    const media = gsap.matchMedia();\n    const position = () => {\n      const card = root.getBoundingClientRect();\n      const cell = anchor.getBoundingClientRect();\n      const width = popup.offsetWidth;\n      const height = popup.offsetHeight;\n      const x = Math.max(12, Math.min(cell.left - card.left + cell.width / 2 - width / 2, root.clientWidth - width - 12));\n      const below = cell.bottom - card.top + 6;\n      const y = Math.max(12, below + height < root.clientHeight - 12 ? below : cell.top - card.top - height - 6);\n      gsap.set(popup, { left: x, top: y });\n    };\n    position();\n    const observer = new ResizeObserver(position);\n    observer.observe(root);\n    observer.observe(popup);\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n      gsap.from(popup, { y: 5, opacity: 0, duration: 0.18, ease: \"power3.out\", clearProps: \"transform,opacity\" });\n    }, rootRef);\n    return () => { observer.disconnect(); media.revert(); };\n  }, { scope: rootRef, dependencies: [popupDay, popupOpen, year, month, density, weekStartsOn], revertOnUpdate: true });\n\n  useEffect(() => () => { closePopupTween.current?.kill(); }, []);\n\n  useEffect(() => {\n    if (!popupOpen) return;\n    const dismiss = (event: PointerEvent) => {\n      const target = event.target as Node;\n      if (!popupRef.current?.contains(target) && !popupAnchor.current?.contains(target)) setPopupDay(null);\n    };\n    document.addEventListener(\"pointerdown\", dismiss, true);\n    return () => document.removeEventListener(\"pointerdown\", dismiss, true);\n  }, [popupOpen]);\n\n  function showPopup(number: number, anchor: HTMLButtonElement) {\n    closePopupTween.current?.kill();\n    popupAnchor.current = anchor;\n    setPopupDay(number);\n  }\n\n  function schedulePopupClose() {\n    closePopupTween.current?.kill();\n    closePopupTween.current = gsap.delayedCall(0.15, () => {\n      if (!popupRef.current?.contains(document.activeElement)) setPopupDay(null);\n    });\n  }\n\n  function pick(next: Date) {\n    closePopupTween.current?.kill();\n    setPopupDay(null);\n    if (value === undefined) setInternalDate(next);\n    onChange?.(new Date(next));\n  }\n\n  function onDayKey(event: KeyboardEvent<HTMLButtonElement>, number: number) {\n    const current = new Date(year, month, number);\n    const steps: Record<string, number> = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };\n    let next: Date;\n    if (event.key in steps) {\n      next = new Date(year, month, number + steps[event.key]);\n    } else if (event.key === \"Home\") next = new Date(year, month, 1);\n    else if (event.key === \"End\") next = new Date(year, month, count);\n    else if (event.key === \"PageUp\" || event.key === \"PageDown\") {\n      next = inMonth(current, year, month + (event.key === \"PageUp\" ? -1 : 1));\n    } else return;\n    event.preventDefault();\n    keyboardFocus.current = true;\n    pick(next);\n  }\n\n  return (\n    <Dialog.Root open={eventFormDate !== null} onOpenChange={(open) => { setEventFormDate(open ? dateKey : null); setPopupDay(null); }}>\n    <div\n      ref={rootRef}\n      data-slot=\"calendar-card\"\n      role=\"group\"\n      aria-label=\"Date picker\"\n      aria-busy={!ready}\n      onKeyDown={(event) => { if (event.key === \"Escape\" && popupOpen) { event.stopPropagation(); popupAnchor.current?.focus(); setPopupDay(null); } }}\n      style={{\n        \"--calendar-accent\": accentColor,\n        \"--calendar-cell-mobile\": density === \"compact\" ? \"36px\" : \"40px\",\n        \"--calendar-cell-desktop\": density === \"compact\" ? \"36px\" : \"44px\",\n      } as CSSProperties}\n      className={cn(\n        \"@container relative w-full max-w-[44rem] rounded-3xl border border-white/10 bg-[#191919] p-4 text-zinc-100 shadow-[0_24px_48px_-16px_#000,0_1px_1px_0_#ffffff18,inset_0_1px_0_0_#ffffff0a] sm:rounded-[2rem] sm:p-8\",\n        className,\n      )}\n    >\n      <div className={cn(\"@min-[480px]:grid @min-[480px]:grid-cols-[184px_minmax(0,1fr)] @min-[480px]:gap-x-8\", !ready && \"invisible\")}>\n        <div className=\"@min-[480px]:col-start-1 @min-[480px]:row-start-1\">\n          <div className={cn(\"flex items-start justify-between gap-3 @min-[480px]:flex-col-reverse @min-[480px]:gap-5\", onEventAdd && \"flex-col-reverse gap-4\")}>\n            <div className=\"flex min-w-0 items-center gap-4 @min-[480px]:block\">\n              <span className=\"mb-3 hidden text-[10px] font-medium uppercase tracking-[0.18em] text-zinc-400 @min-[480px]:block\">Selected date</span>\n              <div data-calendar-day-number className=\"relative h-14 w-[2ch] shrink-0 overflow-hidden text-5xl font-medium leading-none tracking-[-0.07em] tabular-nums @min-[480px]:h-20 @min-[480px]:text-7xl\" aria-hidden=\"true\">\n                <span data-calendar-day-out className=\"absolute inset-x-0 top-0 opacity-0\" />\n                <span data-calendar-day className=\"block\">{day}</span>\n              </div>\n              <button ref={titleRef} type=\"button\" aria-expanded={pickerYear !== null} aria-controls={panelId} aria-label=\"Choose month and year\" onClick={() => setPickerYear(pickerYear === null ? year : null)} className={cn(buttonClass, \"grid min-h-11 flex-1 grid-cols-[1fr_auto] items-center gap-x-2 px-2 py-1 text-left text-base hover:bg-white/5 @min-[480px]:-ml-2 @min-[480px]:flex @min-[480px]:flex-none @min-[480px]:min-h-0 @min-[480px]:gap-1\")}>\n                <span className=\"col-start-1 font-medium @min-[480px]:w-24 @min-[480px]:shrink-0\">{months[month]}</span>\n                <span className=\"col-start-1 row-start-2 text-sm text-zinc-400 @min-[480px]:w-[4ch] @min-[480px]:shrink-0 @min-[480px]:text-base @min-[480px]:tabular-nums\">{year}</span>\n                <ChevronDown aria-hidden=\"true\" size={14} className={cn(\"col-start-2 row-span-2 row-start-1 text-zinc-400\", pickerYear !== null && \"rotate-180\")} />\n              </button>\n            </div>\n            <div className={cn(\"mt-1 flex items-center gap-3\", onEventAdd && \"w-full justify-between\")}>\n              <button type=\"button\" aria-label=\"Go to today\" title=\"Go to today\" onClick={() => { const now = new Date(); pick(new Date(now.getFullYear(), now.getMonth(), now.getDate())); setPickerYear(null); }} className={cn(buttonClass, \"group h-10 w-10 shrink-0 @min-[480px]:h-12 @min-[480px]:w-12 overflow-hidden border border-white/5 bg-[#202020] shadow-[0_3px_8px_#0004] hover:bg-[#292929]\")}>\n                <span aria-hidden=\"true\" className=\"block h-3 bg-[var(--calendar-accent)] shadow-[0_2px_8px_color-mix(in_srgb,var(--calendar-accent)_20%,transparent)]\" />\n                <span aria-hidden=\"true\" className=\"grid grid-cols-7 gap-0.5 p-2\">\n                  {Array.from({ length: 21 }, (_, index) => <span key={index} className={cn(\"h-0.5 rounded-full bg-zinc-600\", index === (day - 1) % 21 && \"bg-[var(--calendar-accent)]\")} />)}\n                </span>\n              </button>\n              {onEventAdd && (\n                <Dialog.Trigger asChild>\n                  <button type=\"button\" className={cn(buttonClass, \"flex min-h-10 shrink-0 items-center gap-2 border border-white/10 bg-white/5 px-3 py-2 text-xs font-medium hover:bg-white/10\")}><Plus size={14} aria-hidden=\"true\" />Add event</button>\n                </Dialog.Trigger>\n              )}\n            </div>\n          </div>\n\n          {showProgress && <div className=\"my-4 flex gap-1.5 @min-[480px]:my-6\" aria-hidden=\"true\">\n            {Array.from({ length: 4 }, (_, index) => (\n              <span key={index} className=\"h-1 flex-1 overflow-hidden rounded-full bg-white/7\">\n                <span data-calendar-fill className=\"block h-full origin-left rounded-full bg-[var(--calendar-accent)]\" style={{ transform: \"scaleX(0)\" }} />\n              </span>\n            ))}\n          </div>}\n        </div>\n\n        <div id={panelId} data-calendar-panel className=\"min-h-72 @min-[480px]:col-start-2 @min-[480px]:row-span-2 @min-[480px]:row-start-1 @min-[480px]:min-h-86 @min-[480px]:border-l @min-[480px]:border-white/5 @min-[480px]:pl-8\" onKeyDown={(event) => { if (event.key === \"Escape\" && pickerYear !== null) { setPickerYear(null); titleRef.current?.focus(); } }}>\n          {pickerYear !== null ? (\n            <div>\n              <div className=\"mb-5 flex items-center justify-between\">\n                <button type=\"button\" aria-label=\"Previous year\" onClick={() => setPickerYear(pickerYear - 1)} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center rounded-full bg-white/5 @min-[480px]:h-9 @min-[480px]:w-9 hover:bg-white/10\")}><ChevronLeft aria-hidden=\"true\" size={16} /></button>\n                <span aria-live=\"polite\" className=\"inline-flex h-7 items-center justify-center text-lg font-semibold leading-7 tabular-nums\">\n                  <span className=\"sr-only\">{pickerYear}</span>\n                  {String(pickerYear).split(\"\").map((digit, index, digits) => (\n                    <span key={digits.length - index} data-calendar-year-digit={digits.length - index - 1} aria-hidden=\"true\" className=\"relative block h-7 w-[1ch] overflow-hidden text-center\">\n                      <span data-calendar-year-out className=\"absolute inset-x-0 top-0 opacity-0\" />\n                      <span data-calendar-year className=\"block\">{digit}</span>\n                    </span>\n                  ))}\n                </span>\n                <button type=\"button\" aria-label=\"Next year\" onClick={() => setPickerYear(pickerYear + 1)} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center rounded-full bg-white/5 @min-[480px]:h-9 @min-[480px]:w-9 hover:bg-white/10\")}><ChevronRight aria-hidden=\"true\" size={16} /></button>\n              </div>\n              <div role=\"group\" aria-label={`Months in ${pickerYear}`} className=\"grid grid-cols-3 gap-x-4 gap-y-3 @min-[480px]:gap-y-5\">\n                {months.map((name, index) => (\n                  <button key={name} type=\"button\" aria-label={`${name} ${pickerYear}`} aria-pressed={index === month && pickerYear === year} onClick={() => { pick(inMonth(date, pickerYear, index)); setPickerYear(null); titleRef.current?.focus(); }} className={cn(buttonClass, \"h-11 text-sm font-medium @min-[480px]:h-11 @min-[480px]:text-base\", index === month && pickerYear === year ? \"bg-[var(--calendar-accent)] text-zinc-950 shadow-[0_2px_12px_color-mix(in_srgb,var(--calendar-accent)_16%,transparent)]\" : \"text-zinc-300 hover:bg-white/5 hover:text-white\")}>\n                    {name.slice(0, 3)}\n                  </button>\n                ))}\n              </div>\n            </div>\n          ) : (\n            <div role=\"group\" aria-label={`${months[month]} ${year}`}>\n              <div aria-hidden=\"true\" className=\"mb-3 grid grid-cols-7 @min-[480px]:mb-5\">\n                {week.map((name) => <span key={name} className=\"text-center text-xs font-medium text-zinc-400\">{name[0]}</span>)}\n              </div>\n              <div data-calendar-grid className=\"relative grid grid-cols-7 gap-y-1 @min-[480px]:gap-y-2\">\n                <span data-calendar-highlight aria-hidden=\"true\" className=\"pointer-events-none absolute left-0 top-0 h-[var(--calendar-cell-mobile)] w-[calc(100%/7)] rounded-lg bg-[var(--calendar-accent)] opacity-0 shadow-[0_2px_12px_color-mix(in_srgb,var(--calendar-accent)_16%,transparent)] @min-[480px]:h-[var(--calendar-cell-desktop)]\" />\n                <span data-calendar-hover aria-hidden=\"true\" className=\"pointer-events-none absolute left-0 top-0 z-[1] h-[var(--calendar-cell-mobile)] w-[calc(100%/7)] rounded-lg border border-white/10 bg-white/5 opacity-0 @min-[480px]:h-[var(--calendar-cell-desktop)]\" />\n                {Array.from({ length: offset }, (_, index) => <span key={`empty-${index}`} aria-hidden=\"true\" />)}\n                {Array.from({ length: count }, (_, index) => {\n                  const number = index + 1;\n                  const dayEvents = eventsByDay.get(number) ?? [];\n                  return (\n                    <button\n                      key={number}\n                      type=\"button\"\n                      data-calendar-date=\"\"\n                      data-selected-day={number === day ? \"\" : undefined}\n                      aria-label={`${months[month]} ${number}, ${year}${dayEvents.length ? `, ${dayEvents.length} ${dayEvents.length === 1 ? \"event\" : \"events\"}` : \"\"}`}\n                      aria-pressed={number === day}\n                      tabIndex={number === day ? 0 : -1}\n                      aria-haspopup={showEvents && dayEvents.length > 0 ? \"dialog\" : undefined}\n                      aria-expanded={showEvents && dayEvents.length > 0 ? popupOpen && popupDay === number : undefined}\n                      aria-controls={popupOpen && popupDay === number ? popupId : undefined}\n                      onPointerEnter={(event) => { if (event.pointerType !== \"touch\" && !eventFormDate) showPopup(number, event.currentTarget); }}\n                      onPointerLeave={schedulePopupClose}\n                      onFocus={(event) => { if (event.currentTarget.matches(\":focus-visible\") && !eventFormDate) showPopup(number, event.currentTarget); }}\n                      onBlur={(event) => { if (!popupRef.current?.contains(event.relatedTarget)) schedulePopupClose(); }}\n                      onClick={(event) => { pick(new Date(year, month, number)); showPopup(number, event.currentTarget); }}\n                      onKeyDown={(event) => onDayKey(event, number)}\n                      className={cn(\n                        buttonClass,\n                        \"relative z-10 flex h-[var(--calendar-cell-mobile)] flex-col items-center justify-center rounded-lg text-base font-medium tabular-nums @min-[480px]:h-[var(--calendar-cell-desktop)]\",\n                        number === day\n                          ? \"text-zinc-950\"\n                          : \"text-zinc-200 hover:text-white\",\n                      )}\n                    >\n                      <span className=\"leading-none\">{number}</span>\n                      {events.length > 0 && (\n                        <span aria-hidden=\"true\" className=\"mt-1 flex h-[3px] items-center justify-center gap-[3px]\">\n                          {dayEvents.slice(0, 3).map((event) => <span key={event.id} className=\"size-[3px] rounded-full\" style={{ backgroundColor: number === day ? \"#09090b\" : event.color ?? accentColor }} />)}\n                        </span>\n                      )}\n                    </button>\n                  );\n                })}\n              </div>\n            </div>\n          )}\n        </div>\n        {popupOpen && (\n          <div ref={popupRef} id={popupId} role=\"dialog\" aria-label={`Events for ${months[month]} ${popupDay}, ${year}`} onPointerEnter={() => closePopupTween.current?.kill()} onPointerLeave={schedulePopupClose} onFocus={() => closePopupTween.current?.kill()} onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget)) schedulePopupClose(); }} className=\"absolute z-30 w-[min(17rem,calc(100%_-_24px))] rounded-2xl border border-white/10 bg-[#242424] p-4 shadow-[0_12px_40px_#0009]\">\n            <div className=\"mb-3 flex items-center justify-between gap-3\">\n              <span className=\"text-xs font-medium\">{months[month].slice(0, 3)} {popupDay} · {popupEvents.length} {popupEvents.length === 1 ? \"event\" : \"events\"}</span>\n              <button type=\"button\" aria-label=\"Close event popup\" onClick={() => { popupAnchor.current?.focus(); setPopupDay(null); }} className={cn(buttonClass, \"flex size-6 items-center justify-center text-zinc-400 hover:bg-white/10\")}><X size={13} aria-hidden=\"true\" /></button>\n            </div>\n            <ul className=\"max-h-40 space-y-2 overflow-y-auto\">\n              {popupEvents.map((event) => (\n                <li key={event.id}>\n                  <EventTag type={onEventClick ? \"button\" : undefined} onClick={onEventClick ? () => onEventClick(event) : undefined} className={cn(\"flex w-full items-start gap-2 rounded-xl px-2 py-2 text-left\", onEventClick && [buttonClass, \"bg-white/4 hover:bg-white/8\"])}>\n                    <span aria-hidden=\"true\" className=\"mt-1 size-1.5 shrink-0 rounded-full\" style={{ backgroundColor: event.color ?? accentColor }} />\n                    <span className=\"min-w-0 flex-1\">\n                      <span className=\"block break-words text-xs font-medium\">{event.title}</span>\n                      <span className=\"mt-1 block text-[10px] text-zinc-400\">{event.time ?? \"All day\"}{event.assignee && ` · ${event.assignee}`}</span>\n                    </span>\n                  </EventTag>\n                </li>\n              ))}\n            </ul>\n            {onEventAdd && <button type=\"button\" onClick={() => { setEventFormDate(`${monthKey}-${String(popupDay).padStart(2, \"0\")}`); setPopupDay(null); }} className={cn(buttonClass, \"mt-3 flex w-full items-center justify-center gap-2 border-t border-white/10 pt-3 text-xs text-zinc-300\")}><Plus size={13} aria-hidden=\"true\" />Add event to this day</button>}\n          </div>\n        )}\n        <div className=\"mt-4 flex items-center justify-between border-t border-white/5 pt-3 @min-[480px]:col-start-1 @min-[480px]:row-start-2 @min-[480px]:self-end\">\n          <button type=\"button\" aria-label=\"Previous month\" onClick={() => { pick(inMonth(date, year, month - 1)); setPickerYear(null); }} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center text-zinc-400 @min-[480px]:h-8 @min-[480px]:w-8 hover:bg-white/5 hover:text-white\")}><ChevronLeft aria-hidden=\"true\" size={15} /></button>\n          <span className=\"flex-1 text-center text-xs font-medium text-zinc-400\">{weekdays[(date.getDay() + 6) % 7].slice(0, 3)}, {months[month].slice(0, 3)} {day}</span>\n          <button type=\"button\" aria-label=\"Advance one month\" onClick={() => { pick(inMonth(date, year, month + 1)); setPickerYear(null); }} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center text-zinc-400 @min-[480px]:h-8 @min-[480px]:w-8 hover:bg-white/5 hover:text-white\")}><ChevronRight aria-hidden=\"true\" size={15} /></button>\n        </div>\n        <span role=\"status\" className=\"sr-only\">Selected date: {selectedLabel}{events.length > 0 && `, ${selectedEvents.length} ${selectedEvents.length === 1 ? \"event\" : \"events\"}`}</span>\n      </div>\n    </div>\n    <Dialog.Portal>\n      <Dialog.Overlay className=\"fixed inset-0 z-50 bg-black/60 backdrop-blur-sm\" />\n      <Dialog.Content onOpenAutoFocus={(event) => { event.preventDefault(); eventTitleRef.current?.focus(); }} style={{ \"--calendar-accent\": accentColor } as CSSProperties} className=\"fixed left-1/2 top-1/2 z-50 max-h-[90dvh] w-[calc(100%_-_2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-3xl border border-white/10 bg-[#202020] p-6 text-zinc-100 shadow-2xl\">\n        <Dialog.Title className=\"text-lg font-semibold\">Add an event</Dialog.Title>\n        <Dialog.Description className=\"mt-1 text-xs text-zinc-400\">Choose a date and optionally assign it to someone.</Dialog.Description>\n        <Dialog.Close aria-label=\"Close add event\" className={cn(buttonClass, \"absolute right-4 top-4 flex size-8 items-center justify-center hover:bg-white/5\")}><X size={16} aria-hidden=\"true\" /></Dialog.Close>\n        <form className=\"mt-5 space-y-4\" onSubmit={(event) => {\n          event.preventDefault();\n          const form = event.currentTarget;\n          const fields = new FormData(form);\n          const title = String(fields.get(\"title\") ?? \"\").trim();\n          const titleInput = form.elements.namedItem(\"title\") as HTMLInputElement;\n          if (!title) { titleInput.setCustomValidity(\"Enter an event title.\"); titleInput.reportValidity(); return; }\n          const eventDate = String(fields.get(\"date\"));\n          onEventAdd?.({ id: `${panelId}-event-${++eventCounter.current}`, date: eventDate, title, time: String(fields.get(\"time\") ?? \"\") || undefined, assignee: String(fields.get(\"assignee\") ?? \"\").trim() || undefined, color: String(fields.get(\"color\")) });\n          pick(new Date(`${eventDate}T00:00:00`));\n          setEventFormDate(null);\n        }}>\n          <label className=\"block space-y-1.5 text-xs text-zinc-300\">Title<input ref={eventTitleRef} name=\"title\" required maxLength={120} placeholder=\"Design review\" onInput={(event) => event.currentTarget.setCustomValidity(\"\")} className={cn(buttonClass, \"block w-full cursor-text rounded-lg border border-white/10 bg-white/5 px-3 py-2.5 text-base text-white sm:text-sm\")} /></label>\n          <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n            <label className=\"block space-y-1.5 text-xs text-zinc-300\">Date<input name=\"date\" type=\"date\" required defaultValue={eventFormDate ?? dateKey} className={cn(buttonClass, \"block w-full min-w-0 rounded-lg border border-white/10 bg-white/5 px-2 py-2.5 text-base [color-scheme:dark] sm:text-xs\")} /></label>\n            <label className=\"block space-y-1.5 text-xs text-zinc-300\">Time<input name=\"time\" type=\"time\" className={cn(buttonClass, \"block w-full min-w-0 rounded-lg border border-white/10 bg-white/5 px-2 py-2.5 text-base [color-scheme:dark] sm:text-xs\")} /></label>\n          </div>\n          <label className=\"block space-y-1.5 text-xs text-zinc-300\">Assign to <span className=\"text-zinc-500\">(optional)</span><input name=\"assignee\" maxLength={80} placeholder=\"Name or team\" className={cn(buttonClass, \"block w-full cursor-text rounded-lg border border-white/10 bg-white/5 px-3 py-2.5 text-base text-white sm:text-sm\")} /></label>\n          <label className=\"flex items-center justify-between text-xs text-zinc-300\">Event color<input name=\"color\" type=\"color\" defaultValue={accentColor.startsWith(\"#\") && accentColor.length === 7 ? accentColor : \"#ff675c\"} className=\"h-8 w-10 cursor-pointer rounded border-0 bg-transparent\" /></label>\n          <div className=\"flex justify-end gap-2 pt-2\">\n            <Dialog.Close className={cn(buttonClass, \"px-4 py-2 text-xs text-zinc-400 hover:bg-white/5\")}>Cancel</Dialog.Close>\n            <button type=\"submit\" className={cn(buttonClass, \"bg-[var(--calendar-accent)] px-4 py-2 text-xs font-semibold text-zinc-950\")}>Save event</button>\n          </div>\n        </form>\n      </Dialog.Content>\n    </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nexport default CalendarCard;\n",
      "type": "registry:ui",
      "target": "@ui/calendar-card.tsx"
    }
  ],
  "type": "registry:ui",
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "title": "Calendar Card"
}
