{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "event-calendar",
  "type": "registry:ui",
  "title": "Event Calendar",
  "description": "Month view that packs multi-day bars and chips into lanes, overflowing into a day list.",
  "categories": [
    "scheduling"
  ],
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "registryDependencies": [
    "https://www.stealth.pm/r/tokens.json",
    "https://www.stealth.pm/r/cn.json",
    "https://www.stealth.pm/r/icons.json",
    "https://www.stealth.pm/r/motion.json",
    "https://www.stealth.pm/r/use-controllable-state.json"
  ],
  "files": [
    {
      "path": "components/ui/event-calendar.tsx",
      "content": "\"use client\";\nimport { Popover } from \"@base-ui/react/popover\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useCallback, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { Calendar as CalendarIcon, ChevronLeft, ChevronRight, Plus, X } from \"@/lib/icons\";\nimport { ease, spring } from \"@/lib/motion\";\nimport { useControllableState } from \"@/lib/use-controllable-state\";\n\n/* ------------------------------------------------------------------ */\n/* Types                                                               */\n/* ------------------------------------------------------------------ */\n\nexport type EventTone = \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\";\n\nexport type CalendarEvent = {\n  id: string;\n  title: string;\n  start: Date;\n  /** Exclusive. An all-day event on the 24th ends at 00:00 on the 25th. */\n  end: Date;\n  allDay?: boolean;\n  location?: string;\n  description?: string;\n  /** Meaning only: a deadline, an outage, a launch. Leave neutral for ordinary meetings. */\n  tone?: EventTone;\n  status?: \"confirmed\" | \"tentative\" | \"canceled\";\n};\n\n/* ------------------------------------------------------------------ */\n/* Plain-date helpers. Local midnight everywhere; day math goes        */\n/* through calendar fields, never milliseconds, so DST can't bite.     */\n/* ------------------------------------------------------------------ */\n\nconst startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate());\nconst startOfMonth = (d: Date) => new Date(d.getFullYear(), d.getMonth(), 1);\nconst addDays = (d: Date, n: number) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);\nconst daysIn = (y: number, m: number) => new Date(y, m + 1, 0).getDate();\nfunction addMonths(d: Date, n: number) {\n  const first = new Date(d.getFullYear(), d.getMonth() + n, 1);\n  return new Date(first.getFullYear(), first.getMonth(), Math.min(d.getDate(), daysIn(first.getFullYear(), first.getMonth())));\n}\nconst dayNum = (d: Date) => d.getFullYear() * 10000 + d.getMonth() * 100 + d.getDate();\nconst cmpDay = (a: Date, b: Date) => dayNum(a) - dayNum(b);\nconst sameDay = (a: Date | null | undefined, b: Date | null | undefined) => !!a && !!b && dayNum(a) === dayNum(b);\nconst diffDays = (a: Date, b: Date) =>\n  Math.round((Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()) - Date.UTC(a.getFullYear(), a.getMonth(), a.getDate())) / 864e5);\nconst monthIndex = (d: Date) => d.getFullYear() * 12 + d.getMonth();\nconst pad = (n: number) => String(n).padStart(2, \"0\");\nconst dayKey = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\nconst fromKey = (k: string) => {\n  const [y, m, d] = k.split(\"-\").map(Number);\n  return new Date(y, m - 1, d);\n};\n\nconst SUNDAY_REGIONS = new Set(\"AG AS BD BR BS BT BW BZ CA CN CO DM DO ET GT GU HK HN ID IL IN JM JP KE KH KR LA MH MM MO MT MX MZ NI NP PA PE PH PK PR PT PY SA SG SV TH TT TW UM US VE VI WS YE ZA ZW\".split(\" \"));\nconst SATURDAY_REGIONS = new Set(\"AE AF BH DJ DZ EG IQ IR JO KW LY OM QA SD SY\".split(\" \"));\nfunction weekStartFor(locale: string): number {\n  try {\n    const l = new Intl.Locale(locale) as Intl.Locale & { getWeekInfo?: () => { firstDay: number }; weekInfo?: { firstDay: number } };\n    const info = l.getWeekInfo?.() ?? l.weekInfo;\n    if (info) return info.firstDay % 7;\n    const region = l.maximize().region ?? \"\";\n    return SUNDAY_REGIONS.has(region) ? 0 : SATURDAY_REGIONS.has(region) ? 6 : 1;\n  } catch {\n    return 0;\n  }\n}\n\n/* Today and the viewer's language only exist in the browser. Both read null /  */\n/* en-US during the server render and settle on hydration without a mismatch.   */\n\nfunction subscribeToday(notify: () => void) {\n  let timer = 0;\n  const arm = () => {\n    const now = new Date();\n    timer = window.setTimeout(() => (notify(), arm()), addDays(startOfDay(now), 1).getTime() - now.getTime() + 50);\n  };\n  arm();\n  const onVisible = () => document.visibilityState === \"visible\" && notify();\n  document.addEventListener(\"visibilitychange\", onVisible);\n  return () => {\n    window.clearTimeout(timer);\n    document.removeEventListener(\"visibilitychange\", onVisible);\n  };\n}\nfunction useToday(override?: Date) {\n  const key = useSyncExternalStore(subscribeToday, () => dayKey(new Date()), () => null);\n  return useMemo(() => (override ? startOfDay(override) : key ? fromKey(key) : null), [key, override]);\n}\nfunction subscribeLanguage(notify: () => void) {\n  window.addEventListener(\"languagechange\", notify);\n  return () => window.removeEventListener(\"languagechange\", notify);\n}\nconst useLocale = (locale?: string) => {\n  const browser = useSyncExternalStore(subscribeLanguage, () => navigator.language, () => \"en-US\");\n  return locale ?? browser;\n};\n\nfunction useSize<T extends HTMLElement>() {\n  const ref = useRef<T>(null);\n  const [size, setSize] = useState({ w: 0, h: 0 });\n  useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const ro = new ResizeObserver(([entry]) => {\n      const { width, height } = entry.contentRect;\n      setSize((s) => (s.w === width && s.h === height ? s : { w: width, h: height }));\n    });\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, []);\n  return [ref, size] as const;\n}\n\n/* ------------------------------------------------------------------ */\n/* Layout: which lane each event takes inside a week row               */\n/* ------------------------------------------------------------------ */\n\ntype Item = { ev: CalendarEvent; first: Date; last: Date; bar: boolean; draft: boolean };\n\n/** Days an event touches. Timed events live on their start day unless they last a full day or more. */\nfunction toItem(ev: CalendarEvent, draft = false): Item {\n  const first = startOfDay(ev.start);\n  const endMs = Math.max(ev.end.getTime(), ev.start.getTime());\n  const long = ev.allDay || endMs - ev.start.getTime() >= 864e5;\n  let last = long ? startOfDay(new Date(endMs - 1)) : first;\n  if (cmpDay(last, first) < 0) last = first;\n  return { ev, first, last, bar: !!long, draft };\n}\n\nexport type WeekSegment = {\n  event: CalendarEvent;\n  /** First and last column this segment covers in the week, 0–6. */\n  from: number;\n  to: number;\n  lane: number;\n  bar: boolean;\n  draft: boolean;\n  /** The event started before this week, or carries on after it. */\n  continuesBefore: boolean;\n  continuesAfter: boolean;\n};\n\nexport type WeekLayout = { visible: WeekSegment[]; hidden: number[]; perDay: CalendarEvent[][] };\n\n/**\n * Packs a week's events into lanes. Multi-day bars claim the top lanes, then\n * timed events stack beneath in start order. When a day has more than fits,\n * the last lane is given to a \"+N more\" count instead of a chip.\n */\nexport function layoutWeek(weekStart: Date, events: CalendarEvent[], lanes: number, draft?: CalendarEvent | null): WeekLayout {\n  const weekEnd = addDays(weekStart, 6);\n  const items = events.map((e) => toItem(e));\n  if (draft) items.push(toItem(draft, true));\n  const segs: WeekSegment[] = [];\n  const perDay: CalendarEvent[][] = Array.from({ length: 7 }, () => []);\n  for (const it of items) {\n    if (cmpDay(it.last, weekStart) < 0 || cmpDay(it.first, weekEnd) > 0) continue;\n    const from = Math.max(0, diffDays(weekStart, it.first));\n    const to = Math.min(6, diffDays(weekStart, it.last));\n    segs.push({ event: it.ev, from, to, lane: 0, bar: it.bar, draft: it.draft, continuesBefore: cmpDay(it.first, weekStart) < 0, continuesAfter: cmpDay(it.last, weekEnd) > 0 });\n    if (!it.draft) for (let c = from; c <= to; c++) perDay[c].push(it.ev);\n  }\n  const rank = (s: WeekSegment) => (s.bar ? 0 : s.draft ? 1 : 2);\n  segs.sort(\n    (x, y) =>\n      rank(x) - rank(y) ||\n      x.from - y.from ||\n      (x.bar ? y.to - y.from - (x.to - x.from) : 0) ||\n      x.event.start.getTime() - y.event.start.getTime() ||\n      y.event.end.getTime() - x.event.end.getTime(),\n  );\n  const occupied: boolean[][] = [];\n  for (const s of segs) {\n    for (let lane = 0; ; lane++) {\n      const row = (occupied[lane] ??= Array(7).fill(false));\n      let free = true;\n      for (let c = s.from; c <= s.to && free; c++) free = !row[c];\n      if (!free) continue;\n      for (let c = s.from; c <= s.to; c++) row[c] = true;\n      s.lane = lane;\n      break;\n    }\n  }\n  const counts = Array(7).fill(0);\n  for (const s of segs) for (let c = s.from; c <= s.to; c++) counts[c]++;\n  const limit = counts.map((n) => (n <= lanes ? lanes : Math.max(0, lanes - 1)));\n  const hidden = Array(7).fill(0);\n  const visible: WeekSegment[] = [];\n  for (const s of segs) {\n    let cap = Infinity;\n    for (let c = s.from; c <= s.to; c++) cap = Math.min(cap, limit[c]);\n    if (s.lane < cap) visible.push(s);\n    else if (s.draft) visible.push({ ...s, lane: Math.max(0, lanes - 1) });\n    else for (let c = s.from; c <= s.to; c++) hidden[c]++;\n  }\n  for (const d of perDay) d.sort((a, b) => Number(!!b.allDay) - Number(!!a.allDay) || a.start.getTime() - b.start.getTime());\n  return { visible, hidden, perDay };\n}\n\n/* ------------------------------------------------------------------ */\n/* Component                                                           */\n/* ------------------------------------------------------------------ */\n\nexport type EventCalendarProps = Omit<React.ComponentProps<\"div\">, \"children\" | \"defaultValue\" | \"onChange\"> & {\n  events?: CalendarEvent[];\n  defaultEvents?: CalendarEvent[];\n  onEventsChange?: (events: CalendarEvent[]) => void;\n  /** Called with the new event after it is created from a day. */\n  onEventCreate?: (event: CalendarEvent) => void;\n  /** Called when an event's details are opened. */\n  onEventClick?: (event: CalendarEvent) => void;\n  /** The visible month (controlled). Any date inside it. */\n  month?: Date;\n  defaultMonth?: Date;\n  onMonthChange?: (month: Date) => void;\n  /** BCP 47 tag. Defaults to the browser's language; drives names, times and the week start. */\n  locale?: string;\n  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;\n  /** Skeleton chips while events load. The grid and navigation stay usable. */\n  loading?: boolean;\n  /** Browse and open events, but no creating. */\n  readOnly?: boolean;\n  /** Always draw six weeks. Off by default, so a five-week month gets taller rows and more room per day. */\n  fixedWeeks?: boolean;\n  /** Pin \"today\" for tests and screenshots. Defaults to the real date. */\n  today?: Date;\n  labels?: { today?: string; previousMonth?: string; nextMonth?: string; newEvent?: string; create?: string; titlePlaceholder?: string };\n};\n\nconst HEAD = 26; // day number row\nconst CHIP = 20;\nconst GAP = 2;\nconst COMPACT_BELOW = 480;\n\ntype Slide = { dir: number; instant: boolean; reduce: boolean };\nconst gridVariants = {\n  enter: (c: Slide) => (c.instant ? { opacity: 1, x: 0 } : { opacity: 0, x: c.reduce ? 0 : c.dir * 24 }),\n  center: (c: Slide) => ({ opacity: 1, x: 0, transition: { duration: c.instant ? 0 : c.reduce ? 0.15 : 0.28, ease: ease.out } }),\n  exit: (c: Slide) =>\n    c.instant ? { opacity: 0, transition: { duration: 0 } } : { opacity: 0, x: c.reduce ? 0 : c.dir * -24, transition: { duration: c.reduce ? 0.1 : 0.18, ease: ease.out } },\n};\nconst titleVariants = {\n  enter: (c: Slide) => (c.instant ? { opacity: 1, y: 0 } : { opacity: 0, y: c.reduce ? 0 : c.dir * 8, filter: c.reduce ? \"blur(0px)\" : \"blur(2px)\" }),\n  center: (c: Slide) => ({ opacity: 1, y: 0, filter: \"blur(0px)\", transition: { duration: c.instant ? 0 : 0.24, ease: ease.out } }),\n  exit: (c: Slide) =>\n    c.instant ? { opacity: 0, transition: { duration: 0 } } : { opacity: 0, y: c.reduce ? 0 : c.dir * -8, filter: c.reduce ? \"blur(0px)\" : \"blur(2px)\", transition: { duration: 0.14, ease: ease.in } },\n};\n\n// A just-created event lands with a hairline ring that fades, so the eye finds it.\nconst LAND_CSS = \"@keyframes stealth-ec-land{from{box-shadow:inset 0 0 0 1px var(--fg-3)}to{box-shadow:inset 0 0 0 1px transparent}}\";\n\nconst toneDot: Record<EventTone, string> = { neutral: \"bg-fg-3\", info: \"bg-info\", success: \"bg-success\", warning: \"bg-warning\", danger: \"bg-danger\" };\nconst toneBar: Record<EventTone, string> = {\n  neutral: \"bg-fg/[0.07] before:bg-fg-3\",\n  info: \"bg-info-soft before:bg-info\",\n  success: \"bg-success-soft before:bg-success\",\n  warning: \"bg-warning-soft before:bg-warning\",\n  danger: \"bg-danger-soft before:bg-danger\",\n};\n\ntype Panel = { kind: \"day\"; date: Date } | { kind: \"event\"; event: CalendarEvent };\n\nexport function EventCalendar({\n  events: eventsProp,\n  defaultEvents = [],\n  onEventsChange,\n  onEventCreate,\n  onEventClick,\n  month: monthProp,\n  defaultMonth,\n  onMonthChange,\n  locale: localeProp,\n  weekStartsOn,\n  loading = false,\n  readOnly = false,\n  fixedWeeks = false,\n  today: todayProp,\n  labels,\n  className,\n  ref,\n  ...rest\n}: EventCalendarProps) {\n  const L = {\n    today: \"Today\",\n    previousMonth: \"Previous month\",\n    nextMonth: \"Next month\",\n    newEvent: \"New event\",\n    create: \"Create event\",\n    titlePlaceholder: \"Add title\",\n    ...labels,\n  };\n  const reduce = !!useReducedMotion();\n  const locale = useLocale(localeProp);\n  const today = useToday(todayProp);\n  const ws = weekStartsOn ?? weekStartFor(locale);\n  const uid = useId();\n\n  const [events, setEvents] = useControllableState({ value: eventsProp, defaultValue: defaultEvents, onChange: onEventsChange });\n  const [monthState, setMonthState] = useControllableState<Date | null>({\n    value: monthProp,\n    defaultValue: defaultMonth ?? null,\n    onChange: (m) => m && onMonthChange?.(m),\n  });\n  const month = monthState ? startOfMonth(monthState) : today ? startOfMonth(today) : null;\n  const [slide, setSlide] = useState<Slide>({ dir: 1, instant: true, reduce });\n\n\n  // Focus: one roving day, moved by arrow keys.\n  const [focusKey, setFocusKey] = useState<string | null>(null);\n  const cellRefs = useRef(new Map<string, HTMLDivElement>());\n  const wantFocus = useRef(false);\n  const returnFocus = useRef<HTMLElement | null>(null);\n\n  // Popovers: one for a day's list or an event's details, one for creating.\n  const [panel, setPanel] = useState<{ open: boolean; value: Panel | null; anchor: Element | null }>({ open: false, value: null, anchor: null });\n  const [draft, setDraft] = useState<{ date: Date; title: string; cell: HTMLElement | null } | null>(null);\n  const [draftEl, setDraftEl] = useState<HTMLElement | null>(null);\n  const [landed, setLanded] = useState<string | null>(null);\n  const landTimer = useRef(0);\n  useEffect(() => () => window.clearTimeout(landTimer.current), []);\n  const suppressClick = useRef(false);\n  // Popovers stay inside the calendar instead of spilling over the page.\n  const [rootEl, setRootEl] = useState<HTMLDivElement | null>(null);\n  const setRoot = useCallback(\n    (el: HTMLDivElement | null) => {\n      setRootEl(el);\n      if (typeof ref === \"function\") ref(el);\n      else if (ref) ref.current = el;\n    },\n    [ref],\n  );\n\n  const fmt = useMemo(() => {\n    const make = (o: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat(locale, o);\n    return {\n      month: make({ month: \"long\" }),\n      year: make({ year: \"numeric\" }),\n      weekday: make({ weekday: \"short\" }),\n      narrow: make({ weekday: \"narrow\" }),\n      dayNum: make({ day: \"numeric\" }),\n      long: make({ weekday: \"long\", day: \"numeric\", month: \"long\" }),\n      longYear: make({ weekday: \"long\", day: \"numeric\", month: \"long\", year: \"numeric\" }),\n      weekdayLong: make({ weekday: \"long\" }),\n      dayMonth: make({ day: \"numeric\", month: \"short\" }),\n      time: make({ hour: \"numeric\", minute: \"2-digit\" }),\n      hour: make({ hour: \"numeric\" }),\n    };\n  }, [locale]);\n  const shortTime = useCallback((d: Date) => (d.getMinutes() ? fmt.time : fmt.hour).format(d), [fmt]);\n  const when = useCallback(\n    (e: CalendarEvent) => {\n      const item = toItem(e);\n      if (e.allDay) return sameDay(item.first, item.last) ? \"All day\" : fmt.dayMonth.formatRange(item.first, item.last);\n      if (item.bar) return `${fmt.dayMonth.format(e.start)}, ${shortTime(e.start)} – ${fmt.dayMonth.format(e.end)}, ${shortTime(e.end)}`;\n      return fmt.time.formatRange(e.start, e.end);\n    },\n    [fmt, shortTime],\n  );\n\n  const weeks = useMemo(() => {\n    if (!month) return null;\n    const lead = (month.getDay() - ws + 7) % 7;\n    const start = addDays(month, -lead);\n    const count = fixedWeeks ? 6 : Math.ceil((lead + daysIn(month.getFullYear(), month.getMonth())) / 7);\n    return Array.from({ length: count }, (_, w) => addDays(start, w * 7));\n  }, [month, ws, fixedWeeks]);\n  const rows = weeks?.length ?? 5;\n\n  const [bodyRef, body] = useSize<HTMLDivElement>();\n  const compact = body.w > 0 && body.w < COMPACT_BELOW;\n  const lanes = body.h ? Math.max(1, Math.floor((body.h / rows - HEAD - 2 + GAP) / (CHIP + GAP))) : 2;\n  const wide = body.w === 0 || body.w / 7 >= 118;\n\n  const draftEvent = useMemo<CalendarEvent | null>(\n    () => (draft ? { id: \"__draft\", title: draft.title, start: draft.date, end: addDays(draft.date, 1), allDay: true } : null),\n    [draft],\n  );\n  const layouts = useMemo(\n    () => (weeks ? weeks.map((w) => layoutWeek(w, loading ? [] : events, lanes, draftEvent)) : null),\n    [weeks, events, lanes, draftEvent, loading],\n  );\n  const monthCount = useMemo(() => {\n    if (!month) return 0;\n    const end = addMonths(month, 1);\n    return events.filter((e) => e.start < end && e.end > month).length;\n  }, [events, month]);\n\n  const tabbableKey = useMemo(() => {\n    if (!month) return null;\n    const inMonth = (k: string | null) => k && monthIndex(fromKey(k)) === monthIndex(month);\n    if (inMonth(focusKey)) return focusKey!;\n    if (today && monthIndex(today) === monthIndex(month)) return dayKey(today);\n    return dayKey(month);\n  }, [focusKey, month, today]);\n\n  // After keyboard navigation re-renders the grid, put focus on the new day.\n  useEffect(() => {\n    if (!wantFocus.current || !tabbableKey) return;\n    wantFocus.current = false;\n    cellRefs.current.get(tabbableKey)?.focus();\n  }, [tabbableKey, month]);\n\n  const goMonth = (next: Date, instant = false) => {\n    if (!month) return;\n    const n = startOfMonth(next);\n    if (monthIndex(n) === monthIndex(month)) return;\n    setSlide({ dir: monthIndex(n) > monthIndex(month) ? 1 : -1, instant, reduce });\n    setDraft(null);\n    setPanel((p) => ({ ...p, open: false }));\n    setMonthState(n);\n  };\n\n  const moveFocus = (to: Date) => {\n    wantFocus.current = true;\n    setFocusKey(dayKey(to));\n    if (month && monthIndex(to) !== monthIndex(month)) goMonth(to, true);\n  };\n\n  const openDay = (date: Date, anchor: Element) => {\n    returnFocus.current = cellRefs.current.get(dayKey(date)) ?? null;\n    setFocusKey(dayKey(date));\n    setPanel({ open: true, value: { kind: \"day\", date }, anchor });\n  };\n  const openEvent = (event: CalendarEvent, anchor: Element) => {\n    returnFocus.current = cellRefs.current.get(dayKey(event.start)) ?? null;\n    setPanel({ open: true, value: { kind: \"event\", event }, anchor });\n    onEventClick?.(event);\n  };\n  const startDraft = (date: Date) => {\n    if (readOnly) return;\n    returnFocus.current = cellRefs.current.get(dayKey(date)) ?? null;\n    setFocusKey(dayKey(date));\n    setPanel((p) => ({ ...p, open: false }));\n    setDraft({ date, title: \"\", cell: cellRefs.current.get(dayKey(date)) ?? null });\n  };\n  const commitDraft = () => {\n    if (!draft || !draft.title.trim()) return;\n    const id = typeof crypto !== \"undefined\" && crypto.randomUUID ? crypto.randomUUID() : `evt-${draft.date.getTime()}-${events.length}`;\n    const created: CalendarEvent = { id, title: draft.title.trim(), start: draft.date, end: addDays(draft.date, 1), allDay: true };\n    setLanded(id);\n    window.clearTimeout(landTimer.current);\n    landTimer.current = window.setTimeout(() => setLanded(null), 1000);\n    setEvents([...events, created]);\n    onEventCreate?.(created);\n    setDraft(null);\n  };\n\n  const onGridKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (!tabbableKey || !(e.target as HTMLElement).dataset?.day) return;\n    const current = fromKey(tabbableKey);\n    const colOf = (d: Date) => (d.getDay() - ws + 7) % 7;\n    const map: Record<string, () => Date> = {\n      ArrowLeft: () => addDays(current, -1),\n      ArrowRight: () => addDays(current, 1),\n      ArrowUp: () => addDays(current, -7),\n      ArrowDown: () => addDays(current, 7),\n      Home: () => addDays(current, -colOf(current)),\n      End: () => addDays(current, 6 - colOf(current)),\n      PageUp: () => addMonths(current, e.shiftKey ? -12 : -1),\n      PageDown: () => addMonths(current, e.shiftKey ? 12 : 1),\n    };\n    if (map[e.key]) {\n      e.preventDefault();\n      moveFocus(map[e.key]());\n    } else if (e.key === \"Enter\" || e.key === \" \") {\n      e.preventDefault();\n      openDay(current, e.target as Element);\n    } else if ((e.key === \"n\" || e.key === \"c\") && !e.metaKey && !e.ctrlKey && !e.altKey && !readOnly) {\n      e.preventDefault();\n      startDraft(current);\n    }\n  };\n\n  const weekdayNames = useMemo(() => {\n    const base = new Date(2026, 0, 4 + ws); // 4 Jan 2026 is a Sunday\n    return Array.from({ length: 7 }, (_, i) => {\n      const d = addDays(base, i);\n      return { short: fmt.weekday.format(d), narrow: fmt.narrow.format(d), long: fmt.weekdayLong.format(d) };\n    });\n  }, [fmt, ws]);\n\n  const monthKey = month ? monthIndex(month) : -1;\n  const isCurrentMonth = !!(today && month && monthIndex(today) === monthIndex(month));\n\n  return (\n    <div\n      data-slot=\"event-calendar\"\n      data-compact={compact || undefined}\n      className={cn(\"flex min-h-[420px] min-w-0 flex-col overflow-hidden rounded-xl border border-line bg-raised shadow-[var(--shadow)]\", className)}\n      onPointerDownCapture={() => {\n        // A click that only dismisses an open popover shouldn't also start a new event.\n        suppressClick.current = panel.open || !!draft;\n      }}\n      ref={setRoot}\n      {...rest}\n    >\n      <style>{LAND_CSS}</style>\n\n      {/* Header */}\n      <div className=\"flex h-12 shrink-0 items-center gap-2 border-b border-line pl-4 pr-2\">\n        <h2 className=\"relative min-w-0 flex-1 overflow-hidden text-[15px] font-medium tracking-[-0.015em]\" aria-live=\"polite\">\n          <span className=\"invisible\">September 0000</span>\n          <AnimatePresence initial={false} custom={slide} mode=\"popLayout\">\n            {month && (\n              <motion.span\n                key={monthKey}\n                custom={slide}\n                variants={titleVariants}\n                initial=\"enter\"\n                animate=\"center\"\n                exit=\"exit\"\n                className=\"absolute inset-y-0 left-0 flex items-center gap-1.5 whitespace-nowrap\"\n              >\n                <span className=\"text-fg\">{fmt.month.format(month)}</span>\n                <span className=\"text-fg-3 tabular\">{fmt.year.format(month)}</span>\n                {!loading && monthCount === 0 && (\n                  <span className=\"ml-1.5 hidden text-[12px] font-normal tracking-normal text-fg-4 sm:inline\">No events</span>\n                )}\n              </motion.span>\n            )}\n          </AnimatePresence>\n        </h2>\n        <button\n          type=\"button\"\n          onClick={() => {\n            if (!today) return;\n            if (isCurrentMonth) {\n              wantFocus.current = true;\n              setFocusKey(dayKey(today));\n            } else goMonth(today);\n          }}\n          disabled={!today}\n          className={cn(\n            \"h-7 rounded-md border border-line-2 px-2.5 text-[12px] font-medium text-fg-2 select-none\",\n            \"outline-none focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-fg-3\",\n            \"transition-[background-color,border-color,color,scale] duration-150 ease-out hover:border-fg-4 hover:bg-hover hover:text-fg active:scale-[0.97] active:duration-75\",\n            \"disabled:pointer-events-none disabled:opacity-50\",\n          )}\n        >\n          {L.today}\n        </button>\n        <div className=\"flex items-center\">\n          <NavButton label={L.previousMonth} onClick={() => month && goMonth(addMonths(month, -1))} disabled={!month}>\n            <ChevronLeft />\n          </NavButton>\n          <NavButton label={L.nextMonth} onClick={() => month && goMonth(addMonths(month, 1))} disabled={!month}>\n            <ChevronRight />\n          </NavButton>\n        </div>\n      </div>\n\n      {/* Grid */}\n      <div role=\"grid\" aria-label={month ? `${fmt.month.format(month)} ${fmt.year.format(month)}` : undefined} aria-busy={loading || !month || undefined} aria-readonly={readOnly || undefined} className=\"flex min-h-0 flex-1 flex-col\" onKeyDown={onGridKeyDown}>\n        <div role=\"row\" className=\"grid shrink-0 grid-cols-7 border-b border-line\">\n          {weekdayNames.map((n, i) => (\n            <div key={i} role=\"columnheader\" aria-label={n.long} className=\"truncate px-2 py-1.5 font-mono text-2xs uppercase tracking-[0.08em] text-fg-3\">\n              {compact ? n.narrow : n.short}\n            </div>\n          ))}\n        </div>\n        <div ref={bodyRef} className=\"relative min-h-0 flex-1 overflow-hidden\">\n          <AnimatePresence initial={false} custom={slide} mode=\"popLayout\">\n            <motion.div\n              key={monthKey}\n              custom={slide}\n              variants={gridVariants}\n              initial=\"enter\"\n              animate=\"center\"\n              exit=\"exit\"\n              role=\"rowgroup\"\n              className=\"absolute inset-0 grid\"\n              style={{ gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))` }}\n            >\n              {Array.from({ length: rows }, (_, w) => {\n                const weekStart = weeks?.[w];\n                const layout = layouts?.[w];\n                return (\n                  <div key={w} role=\"row\" className=\"relative min-h-0\">\n                    {/* Day cells: the click target for creating, the focus target for the keyboard. */}\n                    <div className=\"absolute inset-0 grid grid-cols-7\">\n                      {Array.from({ length: 7 }, (_, c) => {\n                        const date = weekStart ? addDays(weekStart, c) : null;\n                        if (!date || !month) return <div key={c} role=\"gridcell\" className={cn(\"border-line\", c < 6 && \"border-r\", w < rows - 1 && \"border-b\")} />;\n                        const key = dayKey(date);\n                        const outside = monthIndex(date) !== monthIndex(month);\n                        const isToday = sameDay(date, today);\n                        const dayEvents = layout?.perDay[c] ?? [];\n                        const label = `${fmt.longYear.format(date)}${isToday ? `, ${L.today.toLowerCase()}` : \"\"}, ${dayEvents.length === 0 ? \"no events\" : dayEvents.length === 1 ? \"1 event\" : `${dayEvents.length} events`}`;\n                        return (\n                          <div\n                            key={c}\n                            ref={(el) => {\n                              if (el) cellRefs.current.set(key, el);\n                              else cellRefs.current.delete(key);\n                            }}\n                            role=\"gridcell\"\n                            data-day={key}\n                            data-outside={outside || undefined}\n                            data-today={isToday || undefined}\n                            aria-label={label}\n                            aria-current={isToday ? \"date\" : undefined}\n                            tabIndex={key === tabbableKey ? 0 : -1}\n                            onFocus={() => setFocusKey(key)}\n                            onClick={(e) => {\n                              if (suppressClick.current) return void (suppressClick.current = false);\n                              if (compact) openDay(date, e.currentTarget);\n                              else if (readOnly) openDay(date, e.currentTarget);\n                              else startDraft(date);\n                            }}\n                            className={cn(\n                              \"group/day relative min-w-0 border-line outline-none\",\n                              c < 6 && \"border-r\",\n                              w < rows - 1 && \"border-b\",\n                              outside && \"bg-page/50\",\n                              !readOnly && \"cursor-default\",\n                              \"transition-colors duration-150 [-webkit-tap-highlight-color:transparent] hover:bg-fg/[0.025] active:bg-fg/[0.04]\",\n                              \"focus-visible:z-[2] focus-visible:shadow-[inset_0_0_0_1px_var(--fg-3)]\",\n                            )}\n                          >\n                            <div className={cn(\"flex h-[26px] items-center justify-between px-1.5\", compact && \"justify-center\")}>\n                              <span\n                                className={cn(\n                                  \"grid h-5 min-w-5 place-items-center rounded-full px-1 text-[12px] leading-none tabular\",\n                                  isToday ? \"bg-fg font-medium text-frame\" : outside ? \"text-fg-4\" : \"text-fg-2\",\n                                  !isToday && date.getDate() === 1 && \"font-medium text-fg\",\n                                )}\n                              >\n                                {date.getDate() === 1 && !compact && !isToday ? fmt.dayMonth.format(date) : fmt.dayNum.format(date)}\n                              </span>\n                              {!readOnly && !compact && (\n                                <span aria-hidden className=\"grid size-5 place-items-center rounded text-fg-3 opacity-0 transition-opacity duration-150 pointer-fine:group-hover/day:opacity-100\">\n                                  <Plus size={12} />\n                                </span>\n                              )}\n                            </div>\n                            {compact && dayEvents.length > 0 && (\n                              <div aria-hidden className=\"flex justify-center gap-[3px] px-1\">\n                                {dayEvents.slice(0, 3).map((ev) => (\n                                  <span key={ev.id} className={cn(\"size-[5px] rounded-full\", toneDot[ev.tone ?? \"neutral\"], ev.status === \"canceled\" && \"opacity-40\")} />\n                                ))}\n                                {dayEvents.length > 3 && <span className=\"size-[5px] rounded-full bg-fg-4\" />}\n                              </div>\n                            )}\n                          </div>\n                        );\n                      })}\n                    </div>\n\n                    {/* Chips. A pointer layer over the cells; keyboard and screen readers reach events through the day list. */}\n                    {!compact && (\n                      <div\n                        aria-hidden\n                        className=\"pointer-events-none absolute inset-0 grid grid-cols-7 content-start\"\n                        style={{ gridTemplateRows: `${HEAD}px repeat(${lanes}, ${CHIP}px)`, rowGap: GAP }}\n                      >\n                        {(loading || !month) && weekStart\n                          ? Array.from({ length: 7 }, (_, c) => {\n                              const d = addDays(weekStart, c).getDate();\n                              const n = Math.min(lanes, (d * 7 + w) % 4 === 0 ? 2 : (d * 3) % 5 === 0 ? 1 : 0);\n                              return Array.from({ length: n }, (_, l) => (\n                                <span\n                                  key={`${c}-${l}`}\n                                  className=\"mx-1 self-center rounded bg-fg/[0.06] animate-pulse-soft motion-reduce:animate-none\"\n                                  style={{ gridColumn: c + 1, gridRow: l + 2, height: 12, width: l ? \"55%\" : \"80%\" }}\n                                />\n                              ));\n                            })\n                          : null}\n                        <AnimatePresence initial={false}>\n                          {layout?.visible.map((s) =>\n                            s.draft ? (\n                              <motion.div\n                                key=\"__draft\"\n                                ref={setDraftEl}\n                                initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.96, filter: \"blur(2px)\" }}\n                                animate={{ opacity: 1, scale: 1, filter: \"blur(0px)\" }}\n                                exit={{ opacity: 0, transition: { duration: 0.1 } }}\n                                transition={reduce ? { duration: 0.15 } : spring.pop}\n                                className=\"pointer-events-auto mx-1 flex min-w-0 origin-left items-center gap-1.5 rounded-[5px] border border-dashed border-fg-4 bg-raised px-1.5 text-[11.5px] leading-none\"\n                                style={{ gridColumn: `${s.from + 1} / ${s.to + 2}`, gridRow: s.lane + 2 }}\n                              >\n                                <span className={cn(\"truncate\", draft?.title ? \"font-medium text-fg\" : \"text-fg-3\")}>{draft?.title || L.newEvent}</span>\n                              </motion.div>\n                            ) : (\n                              <Chip\n                                key={s.event.id}\n                                seg={s}\n                                wide={wide}\n                                reduce={reduce}\n                                landed={landed === s.event.id}\n                                time={s.bar ? \"\" : shortTime(s.event.start)}\n                                onOpen={(el) => openEvent(s.event, el)}\n                              />\n                            ),\n                          )}\n                        </AnimatePresence>\n                        {layout?.hidden.map((n, c) =>\n                          n > 0 && !(draft && weekStart && sameDay(addDays(weekStart, c), draft.date)) ? (\n                            <button\n                              key={`more-${c}`}\n                              type=\"button\"\n                              tabIndex={-1}\n                              onClick={(e) => weekStart && openDay(addDays(weekStart, c), e.currentTarget)}\n                              className={cn(\n                                \"pointer-events-auto mx-1 flex min-w-0 items-center rounded-[5px] px-1.5 text-left text-[11px] font-medium text-fg-3 select-none\",\n                                \"transition-[background-color,color,scale] duration-150 hover:bg-fg/[0.06] hover:text-fg active:scale-[0.97] active:duration-75\",\n                              )}\n                              style={{ gridColumn: c + 1, gridRow: Math.max(0, lanes - 1) + 2 }}\n                            >\n                              <span className=\"truncate tabular\">+{n} more</span>\n                            </button>\n                          ) : null,\n                        )}\n                      </div>\n                    )}\n                  </div>\n                );\n              })}\n            </motion.div>\n          </AnimatePresence>\n        </div>\n      </div>\n\n      {/* Day list and event details */}\n      <Popover.Root open={panel.open} onOpenChange={(open) => !open && setPanel((p) => ({ ...p, open: false }))}>\n        <Popover.Portal>\n          <Popover.Positioner anchor={panel.anchor} side=\"bottom\" align=\"start\" sideOffset={6} collisionPadding={8} collisionBoundary={rootEl ?? undefined} className=\"z-(--z-popover)\">\n            <Popover.Popup finalFocus={returnFocus} className={popupClass}>\n              {panel.value?.kind === \"day\" && (\n                <DayList\n                  date={panel.value.date}\n                  isToday={sameDay(panel.value.date, today)}\n                  events={eventsOn(events, panel.value.date)}\n                  fmt={fmt}\n                  when={when}\n                  readOnly={readOnly}\n                  newLabel={L.newEvent}\n                  onPick={(ev) => {\n                    onEventClick?.(ev);\n                    setPanel((p) => ({ ...p, open: false }));\n                  }}\n                  onNew={(date) => {\n                    setPanel((p) => ({ ...p, open: false }));\n                    // Let the list close first so the new chip's popover isn't dismissed by it.\n                    window.setTimeout(() => startDraft(date), 0);\n                  }}\n                />\n              )}\n              {panel.value?.kind === \"event\" && <EventDetails event={panel.value.event} fmt={fmt} when={when} />}\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n\n      {/* Quick create, anchored to the draft chip */}\n      <Popover.Root open={!!draft && !!(compact ? draft.cell : draftEl)} onOpenChange={(open) => !open && setDraft(null)}>\n        <Popover.Portal>\n          <Popover.Positioner anchor={compact ? draft?.cell : draftEl} side=\"bottom\" align=\"start\" sideOffset={6} collisionPadding={8} collisionBoundary={rootEl ?? undefined} className=\"z-(--z-popover)\">\n            <Popover.Popup finalFocus={returnFocus} className={popupClass}>\n              {draft && (\n                <form\n                  className=\"flex w-[272px] flex-col gap-2 p-3\"\n                  onSubmit={(e) => {\n                    e.preventDefault();\n                    commitDraft();\n                  }}\n                >\n                  <Popover.Title className=\"sr-only\">{L.newEvent}</Popover.Title>\n                  <input\n                    autoFocus\n                    id={`${uid}-title`}\n                    value={draft.title}\n                    onChange={(e) => setDraft({ ...draft, title: e.target.value })}\n                    placeholder={L.titlePlaceholder}\n                    aria-label=\"Event title\"\n                    autoComplete=\"off\"\n                    enterKeyHint=\"done\"\n                    maxLength={120}\n                    className=\"h-8 w-full rounded-md bg-transparent px-1 text-base font-medium tracking-[-0.015em] text-fg outline-none placeholder:font-normal placeholder:text-fg-4 sm:text-[15px]\"\n                  />\n                  <p className=\"flex items-center gap-2 px-1 text-[12.5px] text-fg-2\">\n                    <CalendarIcon size={14} className=\"shrink-0 text-fg-3\" />\n                    <span className=\"truncate\">{fmt.long.format(draft.date)}</span>\n                    <span className=\"text-fg-4\">·</span>\n                    <span className=\"shrink-0 text-fg-3\">All day</span>\n                  </p>\n                  <div className=\"mt-1 flex items-center justify-end gap-1.5\">\n                    <button type=\"button\" onClick={() => setDraft(null)} className={cn(btnBase, \"px-2.5 text-fg-2 hover:bg-hover hover:text-fg\")}>\n                      Cancel\n                    </button>\n                    <button type=\"submit\" disabled={!draft.title.trim()} className={cn(btnBase, \"bg-fg px-3 text-frame hover:bg-fg/90 disabled:opacity-40\")}>\n                      {L.create}\n                    </button>\n                  </div>\n                </form>\n              )}\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n    </div>\n  );\n}\n\nconst popupClass = cn(\n  \"origin-[var(--transform-origin)] rounded-xl border border-line-2 bg-raised text-fg shadow-pop outline-none\",\n  \"transition-[scale,opacity] duration-200 ease-out-expo data-ending-style:duration-150\",\n  \"data-starting-style:scale-[0.96] data-starting-style:opacity-0 data-ending-style:scale-[0.98] data-ending-style:opacity-0\",\n  \"motion-reduce:data-starting-style:scale-100 motion-reduce:data-ending-style:scale-100\",\n);\nconst btnBase = cn(\n  \"inline-flex h-7 items-center justify-center rounded-md text-[12.5px] font-medium select-none\",\n  \"outline-none focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-fg-3\",\n  \"transition-[background-color,color,scale,opacity] duration-150 ease-out active:scale-[0.97] active:duration-75 disabled:pointer-events-none\",\n);\n\nfunction eventsOn(events: CalendarEvent[], date: Date) {\n  return events\n    .map((e) => toItem(e))\n    .filter((it) => cmpDay(it.first, date) <= 0 && cmpDay(it.last, date) >= 0)\n    .map((it) => it.ev)\n    .sort((a, b) => Number(!!b.allDay) - Number(!!a.allDay) || a.start.getTime() - b.start.getTime());\n}\n\nfunction NavButton({ label, children, ...rest }: React.ComponentProps<\"button\"> & { label: string }) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={label}\n      className={cn(\n        \"relative grid size-8 place-items-center rounded-md text-fg-2 select-none\",\n        \"outline-none focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-fg-3\",\n        \"transition-[background-color,color,scale] duration-150 ease-out hover:bg-hover hover:text-fg active:scale-[0.92] active:duration-75\",\n        \"disabled:pointer-events-none disabled:opacity-40\",\n        \"before:absolute before:-inset-1.5 before:content-[''] pointer-fine:before:hidden\",\n      )}\n      {...rest}\n    >\n      {children}\n    </button>\n  );\n}\n\nfunction Chip({\n  seg,\n  wide,\n  reduce,\n  landed,\n  time,\n  onOpen,\n}: {\n  seg: WeekSegment;\n  wide: boolean;\n  reduce: boolean;\n  landed: boolean;\n  time: string;\n  onOpen: (el: HTMLElement) => void;\n}) {\n  const e = seg.event;\n  const tone = e.tone ?? \"neutral\";\n  const tentative = e.status === \"tentative\";\n  const canceled = e.status === \"canceled\";\n  return (\n    <motion.button\n      type=\"button\"\n      tabIndex={-1}\n      onClick={(ev) => onOpen(ev.currentTarget)}\n      initial={landed ? false : reduce ? { opacity: 0 } : { opacity: 0, scale: 0.96 }}\n      animate={{ opacity: 1, scale: 1 }}\n      exit={{ opacity: 0, transition: { duration: 0.12 } }}\n      transition={reduce ? { duration: 0.15 } : spring.pop}\n      className={cn(\n        \"pointer-events-auto relative flex min-w-0 items-center gap-1.5 rounded-[5px] px-1.5 text-left text-[11.5px] leading-none select-none [-webkit-tap-highlight-color:transparent]\",\n        \"transition-[background-color,scale] duration-150 active:scale-[0.97] active:duration-75\",\n        seg.bar\n          ? cn(\n              \"pl-2.5 font-medium before:absolute before:inset-y-[3px] before:left-[3px] before:w-[2px] before:rounded-full hover:shadow-[inset_0_0_0_1px_var(--line-2)]\",\n              tentative ? \"border border-dashed border-fg-4 before:bg-fg-4\" : toneBar[tone],\n            )\n          : \"hover:bg-fg/[0.06]\",\n        !seg.continuesBefore && \"ml-1\",\n        !seg.continuesAfter && \"mr-1\",\n        seg.continuesBefore && \"rounded-l-none before:hidden\",\n        seg.continuesAfter && \"rounded-r-none\",\n      )}\n      style={{\n        gridColumn: `${seg.from + 1} / ${seg.to + 2}`,\n        gridRow: seg.lane + 2,\n        animation: landed ? \"stealth-ec-land 900ms var(--ease-out-quart)\" : undefined,\n      }}\n    >\n      {!seg.bar && (\n        <span\n          className={cn(\n            \"size-1.5 shrink-0 rounded-full\",\n            tentative ? \"border border-fg-3 bg-transparent\" : toneDot[tone],\n            canceled && \"opacity-40\",\n          )}\n        />\n      )}\n      {!seg.bar && wide && time && <span className=\"shrink-0 text-fg-3 tabular\">{time}</span>}\n      <span className={cn(\"truncate\", seg.bar ? \"font-medium text-fg\" : \"text-fg\", canceled && \"text-fg-4 line-through decoration-fg-4\")}>{e.title}</span>\n    </motion.button>\n  );\n}\n\ntype Fmt = {\n  long: Intl.DateTimeFormat;\n  weekdayLong: Intl.DateTimeFormat;\n  dayNum: Intl.DateTimeFormat;\n  dayMonth: Intl.DateTimeFormat;\n};\n\nfunction DayList({\n  date,\n  isToday,\n  events,\n  fmt,\n  when,\n  readOnly,\n  newLabel,\n  onPick,\n  onNew,\n}: {\n  date: Date;\n  isToday: boolean;\n  events: CalendarEvent[];\n  fmt: Fmt;\n  when: (e: CalendarEvent) => string;\n  readOnly: boolean;\n  newLabel: string;\n  onPick: (e: CalendarEvent) => void;\n  onNew: (d: Date) => void;\n}) {\n  return (\n    <div className=\"relative flex max-h-[min(440px,var(--available-height))] w-[272px] flex-col p-1.5\">\n      <div className=\"flex shrink-0 items-center gap-2.5 py-1.5 pl-2 pr-10\">\n        <span className={cn(\"grid size-8 shrink-0 place-items-center rounded-full text-[14px] font-medium tabular\", isToday ? \"bg-fg text-frame\" : \"bg-hover text-fg\")}>\n          {fmt.dayNum.format(date)}\n        </span>\n        <div className=\"min-w-0\">\n          <Popover.Title className=\"truncate text-[13px] font-medium tracking-[-0.01em] text-fg\">{fmt.weekdayLong.format(date)}</Popover.Title>\n          <Popover.Description className=\"text-[12px] text-fg-3\">\n            {events.length === 0 ? \"Nothing scheduled\" : events.length === 1 ? \"1 event\" : `${events.length} events`}\n          </Popover.Description>\n        </div>\n      </div>\n      {events.length > 0 && (\n        <ul className={cn(\"-mx-0.5 min-h-0 flex-1 overflow-y-auto overscroll-contain px-0.5 pb-px\", events.length > 4 && \"[mask-image:linear-gradient(to_bottom,var(--fg)_calc(100%-24px),transparent)]\")}>\n          {events.map((ev) => (\n            <li key={ev.id}>\n              <button\n                type=\"button\"\n                onClick={() => onPick(ev)}\n                className=\"flex w-full items-start gap-2.5 rounded-lg px-2 py-1.5 text-left outline-none transition-[background-color,scale] duration-150 hover:bg-hover focus-visible:bg-hover focus-visible:outline-solid focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-fg-4 active:scale-[0.98] active:duration-75\"\n              >\n                <span aria-hidden className=\"grid h-[19px] w-2 shrink-0 place-items-center\">\n                  <span\n                    className={cn(\n                      \"rounded-full\",\n                      ev.allDay || toItem(ev).bar ? \"h-2.5 w-[3px]\" : \"size-1.5\",\n                      ev.status === \"tentative\" ? \"border border-fg-3\" : toneDot[ev.tone ?? \"neutral\"],\n                    )}\n                  />\n                </span>\n                <span className=\"min-w-0 flex-1\">\n                  <span className={cn(\"block truncate text-[13px] text-fg\", ev.status === \"canceled\" && \"text-fg-4 line-through\")}>{ev.title}</span>\n                  <span className=\"block truncate text-[12px] text-fg-3 tabular\">\n                    {when(ev)}\n                    {ev.location ? ` · ${ev.location}` : \"\"}\n                    {ev.status === \"tentative\" ? \" · Tentative\" : ev.status === \"canceled\" ? \" · Canceled\" : \"\"}\n                  </span>\n                </span>\n              </button>\n            </li>\n          ))}\n        </ul>\n      )}\n      {!readOnly && (\n        <button\n          type=\"button\"\n          onClick={() => onNew(date)}\n          className=\"mt-1 flex h-8 shrink-0 items-center gap-2 rounded-lg px-2 text-[12.5px] font-medium text-fg-2 outline-none transition-[background-color,color,scale] duration-150 hover:bg-hover hover:text-fg focus-visible:bg-hover focus-visible:text-fg focus-visible:outline-solid focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-fg-4 active:scale-[0.98] active:duration-75\"\n        >\n          <Plus size={14} />\n          {newLabel}\n        </button>\n      )}\n      {/* Last in the DOM so opening the list focuses its first event, not Close. */}\n      <Popover.Close aria-label=\"Close\" className={cn(closeClass, \"absolute right-2.5 top-3\")}>\n        <X size={14} />\n      </Popover.Close>\n    </div>\n  );\n}\n\nconst closeClass = cn(\n  \"grid size-7 place-items-center rounded-md text-fg-3 outline-none select-none\",\n  \"transition-[background-color,color,scale] duration-150 hover:bg-hover hover:text-fg active:scale-[0.92] active:duration-75\",\n  \"focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-fg-3\",\n  \"before:absolute before:-inset-2 before:content-[''] pointer-fine:before:hidden\",\n);\n\nfunction EventDetails({ event, fmt, when }: { event: CalendarEvent; fmt: Fmt; when: (e: CalendarEvent) => string }) {\n  const tone = event.tone ?? \"neutral\";\n  return (\n    <div className=\"relative flex w-[288px] flex-col gap-2.5 p-4 pr-10\">\n      <div className=\"flex items-start gap-2.5\">\n        <span aria-hidden className={cn(\"mt-[3px] h-3.5 w-[3px] shrink-0 rounded-full\", event.status === \"tentative\" ? \"border border-fg-3\" : toneDot[tone])} />\n        <div className=\"min-w-0\">\n          <Popover.Title className={cn(\"text-[14px] font-medium leading-snug tracking-[-0.015em] text-fg [overflow-wrap:anywhere]\", event.status === \"canceled\" && \"text-fg-3 line-through\")}>\n            {event.title}\n          </Popover.Title>\n          <Popover.Description className=\"mt-0.5 text-[12.5px] text-fg-2 tabular\">\n            {fmt.long.format(event.start)}\n            <br />\n            {when(event)}\n          </Popover.Description>\n        </div>\n      </div>\n      {(event.location || event.status === \"tentative\" || event.status === \"canceled\") && (\n        <div className=\"flex flex-col gap-1.5 pl-[13px] text-[12.5px] text-fg-2\">\n          {event.location && (\n            <p className=\"flex items-start gap-2\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden className=\"mt-px shrink-0 text-fg-3\">\n                <path d=\"M8 14s4.5-4.1 4.5-7.75a4.5 4.5 0 0 0-9 0C3.5 9.9 8 14 8 14z\" />\n                <circle cx=\"8\" cy=\"6.25\" r=\"1.5\" />\n              </svg>\n              <span className=\"min-w-0 [overflow-wrap:anywhere]\">{event.location}</span>\n            </p>\n          )}\n          {event.status === \"tentative\" && <p className=\"text-fg-3\">Tentative</p>}\n          {event.status === \"canceled\" && <p className=\"text-fg-3\">Canceled</p>}\n        </div>\n      )}\n      {event.description && <p className=\"line-clamp-3 pl-[13px] text-[12.5px] leading-relaxed text-fg-3\">{event.description}</p>}\n      <Popover.Close aria-label=\"Close\" className={cn(closeClass, \"absolute right-2 top-2\")}>\n        <X size={14} />\n      </Popover.Close>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "docs": "https://www.stealth.pm/ui/event-calendar"
}
