{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "github-activity",
  "title": "GitHub Activity",
  "description": "A Calamansi squircle calendar plotting a year of GitHub contributions, with a drawer stacking the busiest repositories over it.",
  "dependencies": [
    "motion",
    "figma-squircle",
    "react-use-measure"
  ],
  "registryDependencies": [
    "utils",
    "squircle"
  ],
  "files": [
    {
      "path": "components/ui/github-activity.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n  AnimatePresence,\n  motion,\n  useReducedMotion,\n  type Transition,\n} from \"motion/react\";\nimport { Squircle } from \"@/lib/squircle\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ContributionLevel = 0 | 1 | 2 | 3 | 4;\n\nexport type Contribution = {\n  date: string;\n  count: number;\n  level: ContributionLevel;\n};\n\nexport type RepoContribution = {\n  name: string;\n  count: number;\n  logo?: React.ReactNode;\n  href?: string;\n};\n\nexport type GitHubActivityVariant = \"white\" | \"calamansi\" | \"slate\" | \"citrus\";\n\nconst DEFAULT_CELL_SIZE = 11;\nconst DEFAULT_LABEL = \"Top contributions in:\";\nconst DEFAULT_MONTHS = 12;\nconst WEEKS_PER_MONTH = 365.25 / 12 / 7;\nconst STACK_LIMIT = 3;\nconst MIN_CARD_WIDTH = 320;\nconst MIN_LABEL_WEEKS = 3;\n/**\n * The padding either side of the grid, at the widest breakpoint. The width math\n * below has to add it back.\n */\nconst CHROME_WIDTH = 40;\n\nconst gapFor = (cellSize: number) => Math.max(2, Math.round(cellSize / 4));\n// never zero: weeks.slice(-0) would hand back the whole history instead of nothing\nconst weeksFor = (months: number) =>\n  Math.max(1, Math.ceil(months * WEEKS_PER_MONTH));\n\nconst useIsoLayoutEffect =\n  typeof window !== \"undefined\" ? React.useLayoutEffect : React.useEffect;\n\nconst EASE_OUT = [0.22, 1, 0.36, 1] as const;\nconst SPRING = { type: \"spring\", bounce: 0.2, duration: 0.62 } as const;\nconst HEADER_SPRING = { ...SPRING, bounce: 0.45 } as const;\nconst ROW_SPRING = { ...SPRING, bounce: 0.26, delay: 0.08 } as const;\nconst ROW_OFFSET = 16;\nconst CELL_FADE = { duration: 0.2, ease: EASE_OUT } as const;\nconst TOOLTIP_FADE = { duration: 0.14, ease: EASE_OUT } as const;\nconst TOOLTIP_EDGE = 8;\nconst COLUMN_STAGGER = 0.012;\nconst LABEL_BLUR = 6;\nconst LABEL_REVEAL = { duration: 0.45, ease: EASE_OUT } as const;\n\nconst LEVELS = [0, 1, 2, 3, 4] as const;\n\nconst MONTH_NAMES = [\n  \"Jan\",\n  \"Feb\",\n  \"Mar\",\n  \"Apr\",\n  \"May\",\n  \"Jun\",\n  \"Jul\",\n  \"Aug\",\n  \"Sep\",\n  \"Oct\",\n  \"Nov\",\n  \"Dec\",\n];\n\n/**\n * The Calamansi surface: one flat layer, clipped to a squircle by `Squircle`,\n * with the calendar sitting straight on the palette.\n *\n * `calamansi` is the default. `white` is the other end of the range — the only\n * palette that has to read on a light page: white in light mode, near-black in\n * dark. Each palette carries the ink that sits on it, and everything inside tints\n * from `currentColor`.\n */\nconst SURFACE = [\n  \"relative max-w-full p-4 font-runde select-none\",\n  \"sm:p-5\",\n].join(\" \");\n\nconst VARIANTS: Record<GitHubActivityVariant, { paint: string; ink: string }> =\n  {\n    white: {\n      paint: \"bg-white dark:bg-[#1c1c1f]\",\n      ink: \"text-foreground\",\n    },\n    calamansi: {\n      paint: [\n        \"bg-gradient-to-br from-[#8fa37d] via-[#5c7a67] to-[#39564a]\",\n        \"dark:from-[#1b281f] dark:via-[#16221a] dark:to-[#0e1611]\",\n      ].join(\" \"),\n      ink: \"text-white\",\n    },\n    slate: {\n      paint: [\n        \"bg-gradient-to-br from-[#a79cb7] via-[#687396] to-[#4a5a7f]\",\n        \"dark:from-[#1e1b4b] dark:via-[#1e293b] dark:to-[#0f172a]\",\n      ].join(\" \"),\n      ink: \"text-white\",\n    },\n    citrus: {\n      paint: [\n        \"bg-gradient-to-br from-[#d69f7e] via-[#b87152] to-[#7d4128]\",\n        \"dark:from-[#2e170c] dark:via-[#22120b] dark:to-[#140a06]\",\n      ].join(\" \"),\n      ink: \"text-white\",\n    },\n  };\n\n/**\n * The contribution ramp each palette wears, levels 1 to 4 — level 0 stays empty\n * so the surface shows through. Used when no explicit `accent` is passed.\n *\n * The white ramp is mixed from `currentColor`, so the same four steps come out\n * dark on a light page and light on a dark one, following the surface ink.\n */\nconst ACCENTS: Record<GitHubActivityVariant, string[]> = {\n  white: [\n    \"color-mix(in oklab, currentcolor 22%, transparent)\",\n    \"color-mix(in oklab, currentcolor 45%, transparent)\",\n    \"color-mix(in oklab, currentcolor 70%, transparent)\",\n    \"currentcolor\",\n  ],\n  calamansi: [\"#d9f2a6\", \"#b4e84c\", \"#8fc22f\", \"#5f8a14\"],\n  slate: [\"#cfc7e0\", \"#a79cb7\", \"#7a83a8\", \"#4a5a7f\"],\n  citrus: [\"#ffdcb4\", \"#ffbc80\", \"#ff9e3d\", \"#c4691b\"],\n};\n\n/** The content layer above the surface, and the space the drawer reserves. */\nconst CONTENT = \"relative z-10 flex flex-col\";\n\nfunction toMonthLabels(weeks: Contribution[][]) {\n  const labels: (string | null)[] = weeks.map(() => null);\n  const monthAt = (index: number) => weeks[index]?.[0]?.date.slice(5, 7);\n\n  let start = 0;\n  for (let i = 1; i <= weeks.length; i++) {\n    if (i < weeks.length && monthAt(i) === monthAt(start)) continue;\n    // a shorter run is narrower than the label itself, so it would sit under the next month\n    if (i - start >= MIN_LABEL_WEEKS) {\n      labels[start] = MONTH_NAMES[Number(monthAt(start)) - 1] ?? null;\n    }\n    start = i;\n  }\n\n  return labels;\n}\n\nconst LEVEL_OPACITY: Record<ContributionLevel, number> = {\n  0: 0,\n  1: 0.3,\n  2: 0.52,\n  3: 0.76,\n  4: 1,\n};\n\ntype LevelStyle = { backgroundColor: string; opacity: number };\n\ntype HoveredDay = { day: Contribution; x: number; y: number };\n\nconst DATE_FORMAT = new Intl.DateTimeFormat(\"en-US\", {\n  month: \"short\",\n  day: \"numeric\",\n  year: \"numeric\",\n});\n\nfunction describeDay({ count, date }: Contribution) {\n  const noun = count === 1 ? \"contribution\" : \"contributions\";\n  return `${count} ${noun} on ${DATE_FORMAT.format(new Date(`${date}T00:00:00`))}`;\n}\n\nconst CALENDAR_API = \"https://github-contributions-api.jogruber.de/v4\";\nconst EVENTS_API = \"https://api.github.com/users\";\n\ntype ApiDay = { date: string; count: number; level: number };\ntype PushEvent = {\n  type: string;\n  repo?: { name: string };\n  payload?: { commits?: unknown[] };\n};\n\nasync function fetchCalendar(login: string) {\n  const res = await fetch(`${CALENDAR_API}/${login}?y=last`);\n  if (!res.ok) return null;\n\n  const days: ApiDay[] = (await res.json())?.contributions ?? [];\n  if (!days.length) return null;\n\n  // columns are weeks, so the first day has to be a sunday or every column shears\n  const start = days.findIndex(\n    (day) => new Date(`${day.date}T00:00:00Z`).getUTCDay() === 0,\n  );\n\n  return days.slice(start < 0 ? 0 : start).map<Contribution>((day) => ({\n    date: day.date,\n    count: day.count,\n    level: Math.min(4, Math.max(0, day.level)) as ContributionLevel,\n  }));\n}\n\nasync function fetchRepos(login: string): Promise<RepoContribution[]> {\n  const res = await fetch(`${EVENTS_API}/${login}/events/public?per_page=100`);\n  if (!res.ok) return [];\n\n  const events: PushEvent[] = await res.json();\n  const counts = new Map<string, number>();\n\n  for (const event of events) {\n    if (event.type !== \"PushEvent\" || !event.repo) continue;\n    const commits = event.payload?.commits?.length ?? 1;\n    counts.set(event.repo.name, (counts.get(event.repo.name) ?? 0) + commits);\n  }\n\n  return [...counts.entries()]\n    .sort(([, a], [, b]) => b - a)\n    .slice(0, STACK_LIMIT)\n    .map(([fullName, count]) => {\n      const [owner, name] = fullName.split(\"/\");\n      return {\n        name,\n        count,\n        href: `https://github.com/${fullName}`,\n        // github has no repo logo, only an owner avatar, so own repos use the initial\n        logo:\n          owner.toLowerCase() === login.toLowerCase() ? undefined : (\n            // eslint-disable-next-line @next/next/no-img-element\n            <img src={`https://github.com/${owner}.png?size=64`} alt=\"\" />\n          ),\n      };\n    });\n}\n\nfunction useGitHubUser(login?: string) {\n  const [data, setData] = React.useState<{\n    contributions: Contribution[];\n    repos: RepoContribution[];\n  }>();\n\n  React.useEffect(() => {\n    if (!login) return;\n    let active = true;\n\n    Promise.all([fetchCalendar(login), fetchRepos(login)])\n      .then(([contributions, repos]) => {\n        if (active && contributions) setData({ contributions, repos });\n      })\n      .catch(() => {});\n\n    return () => {\n      active = false;\n    };\n  }, [login]);\n\n  return data;\n}\n\nfunction emptyDays(weeks: number): Contribution[] {\n  const today = new Date();\n  return Array.from({ length: weeks * 7 }, (_, i) => {\n    const date = new Date(today);\n    date.setDate(date.getDate() - (weeks * 7 - 1 - i));\n    return {\n      date: date.toISOString().slice(0, 10),\n      count: 0,\n      level: 0 as ContributionLevel,\n    };\n  });\n}\n\nfunction toScale(accent: string | string[]): LevelStyle[] {\n  if (typeof accent === \"string\") {\n    return LEVELS.map((level) => ({\n      backgroundColor: accent,\n      opacity: LEVEL_OPACITY[level],\n    }));\n  }\n\n  const colors = accent.length > 4 ? accent : [\"transparent\", ...accent];\n  return LEVELS.map((level) => {\n    const color = colors[level] ?? colors.at(-1) ?? \"transparent\";\n    return { backgroundColor: color, opacity: color === \"transparent\" ? 0 : 1 };\n  });\n}\n\nfunction toWeeks(contributions: Contribution[]) {\n  const weeks: Contribution[][] = [];\n  for (let i = 0; i < contributions.length; i += 7) {\n    weeks.push(contributions.slice(i, i + 7));\n  }\n  return weeks;\n}\n\nfunction useFittedColumns(cellSize: number, gap: number) {\n  const ref = React.useRef<HTMLDivElement>(null);\n  const [columns, setColumns] = React.useState<number>();\n\n  useIsoLayoutEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n\n    // The observer's first callback lands after layout and before paint, so the\n    // grid is trimmed to the real width without a visible flash.\n    const observer = new ResizeObserver((entries) => {\n      const width = entries[0]?.contentRect.width ?? el.clientWidth;\n      setColumns(Math.max(1, Math.floor((width + gap) / (cellSize + gap))));\n    });\n\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, [cellSize, gap]);\n\n  return [ref, columns] as const;\n}\n\nconst Tooltip = ({\n  hovered,\n  reduceMotion,\n}: {\n  hovered: HoveredDay;\n  reduceMotion: boolean | null;\n}) => {\n  const ref = React.useRef<HTMLDivElement>(null);\n\n  useIsoLayoutEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n\n    // Kept off the viewport edges by writing straight to the node — measuring the\n    // chip is a DOM read, not React state.\n    const half = el.offsetWidth / 2;\n    const edge = TOOLTIP_EDGE + half;\n    el.style.left = `${Math.min(Math.max(hovered.x, edge), window.innerWidth - edge)}px`;\n  }, [hovered]);\n\n  return createPortal(\n    <div\n      ref={ref}\n      className=\"pointer-events-none fixed z-50\"\n      style={{\n        left: hovered.x,\n        top: hovered.y,\n        transform: \"translate(-50%, calc(-100% - 8px))\",\n      }}\n    >\n      <motion.div\n        className=\"font-runde whitespace-nowrap rounded-lg bg-foreground px-2 py-1 text-[11px] font-medium text-background shadow-md\"\n        initial={reduceMotion ? false : { opacity: 0, scale: 0.94 }}\n        animate={{ opacity: 1, scale: 1 }}\n        exit={reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.94 }}\n        transition={reduceMotion ? { duration: 0 } : TOOLTIP_FADE}\n      >\n        {describeDay(hovered.day)}\n      </motion.div>\n    </div>,\n    document.body,\n  );\n};\n\nconst ContributionGrid = ({\n  contributions,\n  scale,\n  cellSize,\n  months,\n  showMonths,\n  label,\n  reduceMotion,\n}: {\n  contributions: Contribution[];\n  scale: LevelStyle[];\n  cellSize: number;\n  months: number;\n  showMonths: boolean;\n  label: string;\n  reduceMotion: boolean | null;\n}) => {\n  const weeks = React.useMemo(() => toWeeks(contributions), [contributions]);\n  const gap = gapFor(cellSize);\n  const [ref, columns] = useFittedColumns(cellSize, gap);\n  const [hovered, setHovered] = React.useState<HoveredDay>();\n\n  const cap = Math.min(weeks.length, weeksFor(months));\n  const visible = weeks.slice(-Math.min(cap, columns ?? cap));\n  const sweepEnd = (visible.length - 1) * COLUMN_STAGGER + CELL_FADE.duration;\n\n  const hover = (day: Contribution) => (event: React.PointerEvent) => {\n    const cell = event.currentTarget.getBoundingClientRect();\n    setHovered({ day, x: cell.left + cell.width / 2, y: cell.top });\n  };\n\n  return (\n    <div\n      ref={ref}\n      data-slot=\"github-activity-grid\"\n      role=\"img\"\n      aria-label={label}\n      className=\"relative\"\n    >\n      {showMonths && (\n        <motion.div\n          className=\"flex justify-center\"\n          style={{ gap, marginBottom: gap }}\n          initial={\n            reduceMotion\n              ? false\n              : { opacity: 0, filter: `blur(${LABEL_BLUR}px)` }\n          }\n          animate={{ opacity: 1, filter: \"blur(0px)\" }}\n          transition={{\n            ...LABEL_REVEAL,\n            delay: reduceMotion ? 0 : sweepEnd,\n          }}\n        >\n          {toMonthLabels(visible).map((month, index) => (\n            <div\n              key={index}\n              className=\"relative h-3 shrink-0\"\n              style={{ width: cellSize }}\n            >\n              {month && (\n                <span className=\"absolute top-0 left-0 text-[10px] leading-none text-current/45\">\n                  {month}\n                </span>\n              )}\n            </div>\n          ))}\n        </motion.div>\n      )}\n\n      <div\n        className=\"flex justify-center overflow-hidden\"\n        style={{ gap }}\n        onPointerLeave={() => setHovered(undefined)}\n      >\n        {visible.map((week, weekIndex) => (\n          <div key={weekIndex} className=\"flex flex-col\" style={{ gap }}>\n            {week.map((day) => (\n              <motion.div\n                key={day.date}\n                onPointerEnter={hover(day)}\n                className=\"shrink-0 rounded-[3px] bg-current/15\"\n                style={{ width: cellSize, height: cellSize }}\n                initial={reduceMotion ? false : { opacity: 0, scale: 0.4 }}\n                animate={{ opacity: 1, scale: 1 }}\n                transition={{\n                  ...CELL_FADE,\n                  delay: reduceMotion ? 0 : weekIndex * COLUMN_STAGGER,\n                }}\n              >\n                <div\n                  className=\"h-full w-full rounded-[3px]\"\n                  style={scale[day.level] ?? scale[0]}\n                />\n              </motion.div>\n            ))}\n          </div>\n        ))}\n      </div>\n\n      <AnimatePresence>\n        {hovered && (\n          <Tooltip\n            key=\"tooltip\"\n            hovered={hovered}\n            reduceMotion={reduceMotion}\n          />\n        )}\n      </AnimatePresence>\n    </div>\n  );\n};\n\nconst Avatar = ({\n  repo,\n  layoutId,\n  transition,\n  className,\n}: {\n  repo: RepoContribution;\n  layoutId: string;\n  transition: Transition;\n  className?: string;\n}) => (\n  <motion.span\n    layoutId={layoutId}\n    transition={transition}\n    className={cn(\n      \"grid size-7 shrink-0 place-items-center overflow-hidden rounded-full bg-white/25 text-[11px] font-medium text-white uppercase ring-2 ring-white/20\",\n      \"[&_img]:size-full [&_img]:object-cover [&_svg]:size-full\",\n      className,\n    )}\n  >\n    {repo.logo ?? repo.name.charAt(0)}\n  </motion.span>\n);\n\nconst RepoRow = ({\n  repo,\n  layoutId,\n  transition,\n}: {\n  repo: RepoContribution;\n  layoutId: string;\n  transition: Transition;\n}) => {\n  const className =\n    \"flex items-center gap-3 rounded-xl mx-2 px-2 py-2 transition-colors hover:bg-white/[0.14]\";\n\n  const content = (\n    <>\n      <Avatar repo={repo} layoutId={layoutId} transition={transition} />\n      <span className=\"flex-1 truncate text-sm text-white\">{repo.name}</span>\n      <span className=\"text-sm tabular-nums text-white/85\">{repo.count}</span>\n    </>\n  );\n\n  return repo.href ? (\n    <a href={repo.href} target=\"_blank\" rel=\"noreferrer\" className={className}>\n      {content}\n    </a>\n  ) : (\n    <div className={className}>{content}</div>\n  );\n};\n\nconst Chevron = ({\n  open,\n  transition,\n}: {\n  open: boolean;\n  transition: Transition;\n}) => (\n  <motion.svg\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"1.5\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    aria-hidden\n    className=\"size-7 text-white/70\"\n    initial={false}\n    animate={{ rotate: open ? 180 : 0 }}\n    transition={transition}\n  >\n    <circle cx=\"12\" cy=\"12\" r=\"10\" />\n    <path d=\"m16 10-4 4-4-4\" />\n  </motion.svg>\n);\n\nexport type GitHubActivityProps = React.ComponentProps<\"div\"> & {\n  /** GitHub login to read the calendar and recent pushes from. */\n  username?: string;\n  /** Pre-fetched calendar. Pass it to skip the network entirely. */\n  contributions?: Contribution[];\n  /** Pre-fetched repository stack. */\n  repos?: RepoContribution[];\n  /** Year shown in the heading. Defaults to the year the data ends on. */\n  year?: number;\n  /** Surface palette. Default: \"calamansi\" */\n  variant?: GitHubActivityVariant;\n  /**\n   * Contribution ramp, levels 1 to 4. A single colour fades through opacity\n   * instead. Defaults to the ramp of the selected variant.\n   */\n  accent?: string | string[];\n  /** Size of one day, in pixels. */\n  cellSize?: number;\n  /** How many months of history to show once there is room for them. */\n  months?: number;\n  /** Show the month labels above the grid. */\n  showMonths?: boolean;\n  /** Label on the repository drawer. */\n  label?: string;\n  /** Open the repository drawer on mount. */\n  defaultOpen?: boolean;\n  /** Controlled drawer state. */\n  open?: boolean;\n  /** Notified when the drawer opens or closes. */\n  onOpenChange?: (open: boolean) => void;\n};\n\n/**\n * A year of GitHub contributions on the Calamansi surface: the calendar sits\n * straight on the palette, and the busiest repositories stack along the bottom,\n * expanding into a drawer over the grid.\n *\n * Give it a `username` and it fetches everything it needs in the browser, or pass\n * `contributions` and `repos` to drive it entirely from your own data.\n */\nconst GitHubActivity = ({\n  className,\n  username,\n  contributions: contributionsProp = [],\n  repos: reposProp = [],\n  year,\n  variant = \"calamansi\",\n  accent,\n  cellSize = DEFAULT_CELL_SIZE,\n  months = DEFAULT_MONTHS,\n  showMonths = false,\n  label = DEFAULT_LABEL,\n  defaultOpen = false,\n  open: openProp,\n  onOpenChange,\n  style,\n  ...props\n}: GitHubActivityProps) => {\n  const reduceMotion = useReducedMotion();\n  const uid = React.useId();\n  const [openState, setOpenState] = React.useState(defaultOpen);\n\n  const open = openProp ?? openState;\n  const toggle = () => {\n    if (openProp === undefined) setOpenState(!open);\n    onOpenChange?.(!open);\n  };\n\n  const needsFetch = !contributionsProp.length || !reposProp.length;\n  const fetched = useGitHubUser(needsFetch ? username : undefined);\n  const placeholder = React.useMemo(\n    () => (username ? emptyDays(weeksFor(months)) : []),\n    [username, months],\n  );\n\n  const contributions = contributionsProp.length\n    ? contributionsProp\n    : (fetched?.contributions ?? placeholder);\n  const repos = reposProp.length ? reposProp : (fetched?.repos ?? []);\n\n  const scale = React.useMemo(\n    () => toScale(accent ?? ACCENTS[variant]),\n    [accent, variant],\n  );\n  const transition = reduceMotion ? { duration: 0 } : SPRING;\n  const headerTransition = reduceMotion ? { duration: 0 } : HEADER_SPRING;\n  const rowTransition = reduceMotion ? { duration: 0 } : ROW_SPRING;\n\n  const kick = reduceMotion ? {} : { x: ROW_OFFSET, y: ROW_OFFSET };\n  const listMotion = {\n    initial: { opacity: 0, ...kick },\n    animate: { opacity: 1, x: 0, y: 0 },\n    exit: { opacity: 0, ...kick },\n  };\n\n  const total = React.useMemo(\n    () => contributions.reduce((sum, day) => sum + day.count, 0),\n    [contributions],\n  );\n\n  const parsedYear = Number(contributions.at(-1)?.date.slice(0, 4));\n  const displayYear = year ?? (Number.isFinite(parsedYear) ? parsedYear : null);\n  const heading = `${total} contributions${displayYear ? ` in ${displayYear}` : \"\"}`;\n\n  const gap = gapFor(cellSize);\n  const columns = Math.min(\n    Math.ceil(contributions.length / 7),\n    weeksFor(months),\n  );\n  const width = Math.max(\n    MIN_CARD_WIDTH,\n    columns * (cellSize + gap) - gap + CHROME_WIDTH,\n  );\n\n  return (\n    <div\n      data-slot=\"github-activity\"\n      data-variant={variant}\n      className={cn(SURFACE, VARIANTS[variant].ink, className)}\n      style={{ width, ...style }}\n      {...props}\n    >\n      <Squircle className={VARIANTS[variant].paint} />\n\n      <div className={cn(CONTENT, repos.length > 0 && \"pb-[76px]\")}>\n        <p className=\"mb-4 px-1.5 text-base font-bold tracking-tight\">\n          {heading}\n        </p>\n\n        <ContributionGrid\n          contributions={contributions}\n          scale={scale}\n          cellSize={cellSize}\n          months={months}\n          showMonths={showMonths}\n          label={heading}\n          reduceMotion={reduceMotion}\n        />\n\n        {repos.length > 0 && (\n          <motion.div\n            layout\n            id={`${uid}-panel`}\n            data-slot=\"github-activity-panel\"\n            data-state={open ? \"open\" : \"closed\"}\n            className={cn(\n              // sits well below the glass window, so the drawer reads as its own layer\n              \"absolute inset-x-2 bottom-2 overflow-hidden rounded-[18px] bg-black/65 shadow-[0_16px_36px_-16px_rgba(0,0,0,0.7)] ring-1 ring-white/25 backdrop-blur-xl\",\n              open && \"top-2\",\n            )}\n            transition={transition}\n          >\n            <motion.div\n              layout=\"position\"\n              transition={headerTransition}\n              className=\"flex items-center justify-between gap-3 px-4 py-3\"\n            >\n              <span className=\"truncate text-sm font-medium text-white\">\n                {label}\n              </span>\n\n              <div className=\"flex items-center gap-3\">\n                {!open && (\n                  <div className=\"flex items-center\">\n                    {repos.slice(0, STACK_LIMIT).map((repo, index) => (\n                      <Avatar\n                        key={index}\n                        repo={repo}\n                        layoutId={`${uid}-${index}`}\n                        transition={transition}\n                        className=\"-ml-2 first:ml-0\"\n                      />\n                    ))}\n                  </div>\n                )}\n\n                <button\n                  type=\"button\"\n                  onClick={toggle}\n                  aria-expanded={open}\n                  aria-controls={`${uid}-panel`}\n                  aria-label={\n                    open ? \"Hide top repositories\" : \"Show top repositories\"\n                  }\n                  className=\"grid size-7 shrink-0 cursor-pointer place-items-center rounded-full bg-white/15 transition-colors hover:bg-white/25\"\n                >\n                  <Chevron open={open} transition={transition} />\n                </button>\n              </div>\n            </motion.div>\n\n            <AnimatePresence initial={false} mode=\"popLayout\">\n              {open && (\n                <motion.ul\n                  key=\"list\"\n                  layout=\"position\"\n                  {...listMotion}\n                  transition={rowTransition}\n                  className=\"px-0.5 pb-1\"\n                >\n                  {repos.map((repo, index) => (\n                    <li key={index}>\n                      <RepoRow\n                        repo={repo}\n                        layoutId={`${uid}-${index}`}\n                        transition={transition}\n                      />\n                    </li>\n                  ))}\n                </motion.ul>\n              )}\n            </AnimatePresence>\n          </motion.div>\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport { GitHubActivity };\nexport default GitHubActivity;\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}