{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "weekday-heatmap",
  "type": "registry:component",
  "title": "WeekdayHeatmap",
  "description": "Weekday × hour-of-day matrix with Sum row/column and independent colour scales.",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "src/components/heatmap/weekday-heatmap.tsx",
      "type": "registry:component",
      "target": "components/heatmap/weekday-heatmap.tsx",
      "content": "import type { Locale, Day as WeekDay } from \"date-fns\";\nimport { format } from \"date-fns\";\nimport type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport { createContext, Fragment, use, useMemo } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type WeekdayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;\n\nexport type WeekdayHour =\n  | 0\n  | 1\n  | 2\n  | 3\n  | 4\n  | 5\n  | 6\n  | 7\n  | 8\n  | 9\n  | 10\n  | 11\n  | 12\n  | 13\n  | 14\n  | 15\n  | 16\n  | 17\n  | 18\n  | 19\n  | 20\n  | 21\n  | 22\n  | 23;\n\nexport type WeekdayHourlyActivity = {\n  weekday: number; // 0–6 (Sun–Sat)\n  hour: number; // 0–23\n  value: number;\n};\n\ntype WeekdayHourlyActivityWithLevel = WeekdayHourlyActivity & {\n  level: number;\n};\n\nexport type WeekdayExtraRowSpec = {\n  label: ReactNode;\n  compute: (data: WeekdayHourlyActivity[]) => number[]; // length 24\n};\n\nexport type WeekdayExtraColumnSpec = {\n  label: ReactNode;\n  compute: (data: WeekdayHourlyActivity[]) => number[]; // length 7, indexed by weekday 0–6\n};\n\ntype ResolvedExtraRow = {\n  label: ReactNode;\n  values: { hour: number; value: number; level: number }[];\n};\n\ntype ResolvedExtraColumn = {\n  label: ReactNode;\n  values: { weekday: number; value: number; level: number }[];\n};\n\ntype WeekdayHeatmapContextType = {\n  data: WeekdayHourlyActivityWithLevel[];\n  extraRow: ResolvedExtraRow | null;\n  extraColumn: ResolvedExtraColumn | null;\n  blockMargin: number;\n  blockRadius: number;\n  blockSize: number;\n  blockAspectRatio: number;\n  blockWidth: number;\n  fontSize: number;\n  labels: WeekdayHeatmapLabels;\n  labelWidth: number;\n  labelHeight: number;\n  levels: number;\n  isNormalized: boolean;\n  totalCount: number;\n  width: number;\n  height: number;\n  weekStart: WeekDay;\n  colors?: ColorConfig;\n};\n\nexport type WeekdayHeatmapLabels = {\n  hours?: string[];\n  endHour?: string | null;\n  weekdays?: string[];\n  stat?: string; // Stat text template. Placeholder: {{value}}\n  cellLabel?: string; // aria-label template. Placeholders: {{weekday}}, {{hour}}, {{value}}\n  heatmapLabel?: string; // aria-label for the heatmap SVG\n  legendLabel?: string; // aria-label for the legend fieldset\n  legendLevelLabel?: string; // aria-label template for legend swatches. Placeholder: {{level}}\n};\n\nexport type ColorConfig = {\n  empty?: string;\n  scale?: string;\n};\n\n// Non-normalized: 1 empty + (levels-1) colored steps. Normalized: levels colored steps, no empty.\nconst colorStepCount = (levels: number, isNormalized: boolean) =>\n  Math.max(1, isNormalized ? levels : levels - 1);\n\nconst calculateLevel = (\n  value: number,\n  minValue: number,\n  maxValue: number,\n  levels: number,\n  isNormalized: boolean,\n): number => {\n  const steps = colorStepCount(levels, isNormalized);\n  if (!Number.isFinite(value)) return isNormalized ? 1 : 0;\n  if (isNormalized) {\n    if (maxValue <= minValue) return 1;\n    const percentage = (value - minValue) / (maxValue - minValue);\n    return Math.max(1, Math.min(steps, Math.ceil(percentage * steps)));\n  }\n  if (value <= 0 || maxValue <= 0) return 0;\n  const percentage = value / maxValue;\n  return Math.max(1, Math.min(steps, Math.ceil(percentage * steps)));\n};\n\nconst getLevelFill = (\n  level: number,\n  levels: number,\n  isNormalized: boolean,\n  highlighted = false,\n  colors?: ColorConfig,\n): string => {\n  const emptyColor = colors?.empty ?? \"var(--color-secondary)\";\n  const scaleColor = colors?.scale ?? \"var(--color-chart-1)\";\n\n  if (level === 0) return emptyColor;\n  const steps = colorStepCount(levels, isNormalized);\n  const opacity =\n    steps === 1 ? 100 : Math.round(20 + ((level - 1) * 80) / (steps - 1));\n  const finalOpacity = highlighted ? Math.round(opacity * 0.6) : opacity;\n  return `color-mix(in oklch, ${scaleColor} ${finalOpacity}%, transparent)`;\n};\n\nconst DEFAULT_HOUR_LABELS = Array.from({ length: 24 }, (_, i) =>\n  i.toString().padStart(2, \"0\"),\n);\n\nconst TWELVE_HOUR_LABELS = Array.from({ length: 24 }, (_, i) => {\n  if (i === 0) return \"AM\";\n  if (i < 12) return String(i);\n  if (i === 12) return \"PM\";\n  return String(i - 12);\n});\n\nconst HOURS = Array.from({ length: 24 }, (_, i) => i);\n\nconst DEFAULT_WEEKDAY_LABELS = [\n  \"Sun\",\n  \"Mon\",\n  \"Tue\",\n  \"Wed\",\n  \"Thu\",\n  \"Fri\",\n  \"Sat\",\n];\n\nconst generateWeekdayLabels = (locale: Locale): string[] =>\n  Array.from({ length: 7 }, (_, i) => {\n    const date = new Date(2000, 0, 2 + i); // Jan 2, 2000 is Sunday\n    return format(date, \"EEE\", { locale });\n  });\n\nconst EMPTY_STYLE: CSSProperties = {};\nconst LABEL_MARGIN = 8;\nconst PADDING = 20;\n\nconst WeekdayHeatmapContext = createContext<WeekdayHeatmapContextType | null>(\n  null,\n);\n\nconst useWeekdayHeatmap = () => {\n  const context = use(WeekdayHeatmapContext);\n\n  if (!context) {\n    throw new Error(\n      \"WeekdayHeatmap components must be used within a WeekdayHeatmap\",\n    );\n  }\n\n  return context;\n};\n\nexport type WeekdayHeatmapProps = HTMLAttributes<HTMLDivElement> & {\n  data: WeekdayHourlyActivity[];\n  weekStart?: WeekDay;\n  use12Hour?: boolean;\n  blockSize?: number;\n  blockMargin?: number;\n  blockRadius?: number;\n  blockAspectRatio?: number;\n  levels?: number;\n  isNormalized?: boolean;\n  colors?: ColorConfig;\n  locale?: Locale;\n  labels?: WeekdayHeatmapLabels;\n  fontSize?: number;\n  emptyState?: ReactNode;\n  totalCount?: number;\n  extraRow?: WeekdayExtraRowSpec;\n  extraColumn?: WeekdayExtraColumnSpec;\n  style?: CSSProperties;\n  className?: string;\n  children: ReactNode;\n};\n\n/**\n * Weekday Heatmap\n *\n * A GitHub punch card style heatmap showing activity distribution by weekday (rows) and hour (columns).\n * Renders a 7 × 24 grid with optional extra row (aggregated per hour) and extra column (aggregated per weekday).\n *\n * @example\n * ```tsx\n * <WeekdayHeatmap data={data} weekStart={1} use12Hour>\n *   <WeekdayHeatmapBody>\n *     {({ activity }) => (\n *       <WeekdayHeatmapBlock activity={activity} />\n *     )}\n *   </WeekdayHeatmapBody>\n *   <WeekdayHeatmapFooter>\n *     <WeekdayHeatmapStat />\n *     <WeekdayHeatmapLegend />\n *   </WeekdayHeatmapFooter>\n * </WeekdayHeatmap>\n * ```\n *\n * @param data - Array of activities with weekday (0-6 for Sun-Sat), hour (0-23), and numeric value\n * @param weekStart - First day of week (0=Sunday, 1=Monday). Default: 0\n * @param use12Hour - Use 12-hour format for hour labels. Default: false\n * @param blockSize - Block height in pixels. Default: 24\n * @param blockAspectRatio - Block width/height ratio. Default: 1 (square)\n * @param levels - Total number of legend cells (including empty when not normalized). Default: 5\n * @param isNormalized - When true, uses min-max normalization across the dataset (suitable for signed values like temperature). When false (default), treats 0 as empty and scales from 0 to max.\n * @param extraRow - Appends an aggregated row below the grid. `compute` receives all activities and must return 24 values (one per hour). Rendered through `renderExtraRow` on the body, or falls back to `children`.\n * @param extraColumn - Appends an aggregated column to the right of the grid. `compute` receives all activities and must return 7 values indexed by weekday 0–6. Rendered through `renderExtraColumn` on the body, or falls back to `children`.\n */\nexport const WeekdayHeatmap = ({\n  data,\n  weekStart = 0,\n  use12Hour = false,\n  blockSize = 24,\n  blockMargin = 4,\n  blockRadius = 2,\n  blockAspectRatio = 1,\n  levels: levelsProp = 5,\n  isNormalized = false,\n  colors,\n  locale,\n  labels: labelsProp,\n  fontSize = 14,\n  emptyState,\n  totalCount: totalCountProp,\n  extraRow,\n  extraColumn,\n  style = EMPTY_STYLE,\n  className,\n  children,\n  ...props\n}: WeekdayHeatmapProps) => {\n  const levels = Math.max(1, levelsProp);\n\n  const dataWithLevels = useMemo((): WeekdayHourlyActivityWithLevel[] => {\n    if (data.length === 0) return [];\n\n    const maxRegular = data.reduce((m, d) => Math.max(m, d.value), 1);\n    const minRegular = isNormalized\n      ? data.reduce((m, d) => Math.min(m, d.value), Infinity)\n      : 0;\n\n    return data.map((activity) => ({\n      ...activity,\n      level: calculateLevel(\n        activity.value,\n        minRegular,\n        maxRegular,\n        levels,\n        isNormalized,\n      ),\n    }));\n  }, [data, levels, isNormalized]);\n\n  const resolvedExtraRow = useMemo<ResolvedExtraRow | null>(() => {\n    if (!extraRow) return null;\n    const values = extraRow.compute(data);\n    const maxVal = values.reduce((m, v) => Math.max(m, v), 1);\n    const minVal = isNormalized\n      ? values.reduce((m, v) => Math.min(m, v), Infinity)\n      : 0;\n    return {\n      label: extraRow.label,\n      values: values.map((value, hour) => ({\n        hour,\n        value,\n        level: calculateLevel(value, minVal, maxVal, levels, isNormalized),\n      })),\n    };\n  }, [extraRow, data, levels, isNormalized]);\n\n  const resolvedExtraColumn = useMemo<ResolvedExtraColumn | null>(() => {\n    if (!extraColumn) return null;\n    const values = extraColumn.compute(data);\n    const maxVal = values.reduce((m, v) => Math.max(m, v), 1);\n    const minVal = isNormalized\n      ? values.reduce((m, v) => Math.min(m, v), Infinity)\n      : 0;\n    return {\n      label: extraColumn.label,\n      values: values.map((value, weekday) => ({\n        weekday,\n        value,\n        level: calculateLevel(value, minVal, maxVal, levels, isNormalized),\n      })),\n    };\n  }, [extraColumn, data, levels, isNormalized]);\n\n  const labels = useMemo<WeekdayHeatmapLabels>(() => {\n    const weekdayLabels = locale\n      ? generateWeekdayLabels(locale)\n      : DEFAULT_WEEKDAY_LABELS;\n\n    return {\n      hours: use12Hour ? TWELVE_HOUR_LABELS : DEFAULT_HOUR_LABELS,\n      endHour: use12Hour ? \"12\" : \"00\",\n      weekdays: weekdayLabels,\n      cellLabel: \"{{weekday}} {{hour}}: {{value}}\",\n      heatmapLabel: \"Activity heatmap by weekday and hour\",\n      legendLabel: \"Activity intensity legend\",\n      legendLevelLabel: \"{{level}} contributions\",\n      ...labelsProp,\n    };\n  }, [locale, use12Hour, labelsProp]);\n\n  const labelWidth = fontSize * 3.5 + LABEL_MARGIN;\n\n  const totalCount =\n    typeof totalCountProp === \"number\"\n      ? totalCountProp\n      : dataWithLevels.reduce((sum, a) => sum + a.value, 0);\n\n  const blockWidth = blockSize * blockAspectRatio;\n  const labelHeight = fontSize + LABEL_MARGIN;\n  const hasExtraColumn = resolvedExtraColumn !== null;\n  const hasExtraRow = resolvedExtraRow !== null;\n  const width =\n    24 * (blockWidth + blockMargin) +\n    (hasExtraColumn ? blockSize / 2 + (blockWidth + blockMargin) * 2 : 0) -\n    blockMargin +\n    labelWidth;\n  const rowCount = hasExtraRow ? 8 : 7;\n  const extraRowGap = hasExtraRow ? blockSize + blockMargin : 0;\n  const height =\n    rowCount * (blockSize + blockMargin) +\n    extraRowGap -\n    blockMargin +\n    labelHeight;\n\n  const contextValue = useMemo<WeekdayHeatmapContextType>(\n    () => ({\n      data: dataWithLevels,\n      extraRow: resolvedExtraRow,\n      extraColumn: resolvedExtraColumn,\n      blockMargin,\n      blockRadius,\n      blockSize,\n      blockAspectRatio,\n      blockWidth,\n      fontSize,\n      labels,\n      labelWidth,\n      labelHeight,\n      levels,\n      isNormalized,\n      totalCount,\n      width,\n      height,\n      weekStart,\n      colors,\n    }),\n    [\n      dataWithLevels,\n      resolvedExtraRow,\n      resolvedExtraColumn,\n      blockMargin,\n      blockRadius,\n      blockSize,\n      blockAspectRatio,\n      blockWidth,\n      fontSize,\n      labels,\n      labelWidth,\n      labelHeight,\n      levels,\n      isNormalized,\n      totalCount,\n      width,\n      height,\n      weekStart,\n      colors,\n    ],\n  );\n\n  if (data.length === 0) {\n    return emptyState ? emptyState : null;\n  }\n\n  return (\n    <WeekdayHeatmapContext value={contextValue}>\n      <div\n        data-slot=\"weekday-heatmap\"\n        className={cn(\"flex w-max max-w-full flex-col gap-2 p-4\", className)}\n        style={{ fontSize, ...style }}\n        {...props}\n      >\n        {children}\n      </div>\n    </WeekdayHeatmapContext>\n  );\n};\n\nexport type WeekdayHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {\n  activity: WeekdayHourlyActivityWithLevel;\n  extra?: \"row\" | \"column\";\n  highlighted?: boolean;\n  onCellClick?: (activity: WeekdayHourlyActivityWithLevel) => void;\n  onCellHover?: (activity: WeekdayHourlyActivityWithLevel | null) => void;\n  className?: string;\n};\n\nexport const WeekdayHeatmapBlock = ({\n  ref,\n  activity,\n  extra,\n  highlighted = false,\n  onCellClick,\n  onCellHover,\n  onClick,\n  onKeyDown,\n  onMouseEnter,\n  onMouseLeave,\n  className,\n  style: styleProp,\n  ...props\n}: WeekdayHeatmapBlockProps & {\n  ref?: React.RefObject<SVGRectElement | null>;\n}) => {\n  const {\n    blockSize,\n    blockWidth,\n    blockMargin,\n    blockRadius,\n    labelWidth,\n    labelHeight,\n    levels,\n    isNormalized,\n    weekStart,\n    labels,\n    colors,\n  } = useWeekdayHeatmap();\n\n  const level = Math.max(\n    0,\n    Math.min(colorStepCount(levels, isNormalized), activity.level),\n  );\n\n  const isExtraRow = extra === \"row\";\n  const isExtraColumn = extra === \"column\";\n\n  const rowIndex = isExtraRow ? 7 : (activity.weekday - weekStart + 7) % 7;\n  const extraRowGap = isExtraRow ? blockSize + blockMargin : 0;\n  const yPosition =\n    labelHeight + (blockSize + blockMargin) * rowIndex + extraRowGap;\n\n  const extraColumnGap = isExtraColumn ? blockWidth + blockMargin : 0;\n  const xPosition = isExtraColumn\n    ? labelWidth +\n      24 * (blockWidth + blockMargin) +\n      blockSize / 2 +\n      extraColumnGap\n    : labelWidth + (blockWidth + blockMargin) * activity.hour;\n\n  const weekdayToken = isExtraRow\n    ? \"extra\"\n    : (labels.weekdays?.[activity.weekday] ?? String(activity.weekday));\n  const hourToken = isExtraColumn\n    ? \"extra\"\n    : `${String(activity.hour).padStart(2, \"0\")}:00`;\n  const ariaLabel = (labels.cellLabel ?? \"{{weekday}} {{hour}}: {{value}}\")\n    .replace(\"{{weekday}}\", weekdayToken)\n    .replace(\"{{hour}}\", hourToken)\n    .replace(\"{{value}}\", String(activity.value));\n\n  return (\n    <rect\n      ref={ref}\n      data-slot=\"weekday-heatmap-block\"\n      role={onCellClick ? \"button\" : \"img\"}\n      tabIndex={onCellClick ? 0 : -1}\n      aria-label={ariaLabel}\n      className={cn(\n        \"motion-safe:transition-opacity motion-safe:hover:opacity-70\",\n        onCellClick &&\n          \"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n        className,\n      )}\n      data-value={activity.value}\n      data-weekday={isExtraRow ? \"extra\" : activity.weekday}\n      data-hour={isExtraColumn ? \"extra\" : activity.hour}\n      data-level={level}\n      data-highlighted={highlighted || undefined}\n      height={blockSize}\n      rx={blockRadius}\n      ry={blockRadius}\n      width={blockWidth}\n      x={xPosition}\n      y={yPosition}\n      style={{\n        fill: getLevelFill(level, levels, isNormalized, highlighted, colors),\n        ...styleProp,\n      }}\n      onClick={(event) => {\n        onCellClick?.(activity);\n        onClick?.(event);\n      }}\n      onKeyDown={(event) => {\n        if (onCellClick && (event.key === \"Enter\" || event.key === \" \")) {\n          event.preventDefault();\n          onCellClick(activity);\n        }\n        onKeyDown?.(event);\n      }}\n      onMouseEnter={(event) => {\n        onCellHover?.(activity);\n        onMouseEnter?.(event);\n      }}\n      onMouseLeave={(event) => {\n        onCellHover?.(null);\n        onMouseLeave?.(event);\n      }}\n      {...props}\n    />\n  );\n};\nWeekdayHeatmapBlock.displayName = \"WeekdayHeatmapBlock\";\n\nexport type WeekdayHeatmapBodyProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  hideHourLabels?: boolean;\n  hideWeekdayLabels?: boolean;\n  className?: string;\n  labelClassName?: string;\n  children: (props: {\n    activity: WeekdayHourlyActivityWithLevel;\n    weekdayIndex: number;\n  }) => ReactNode;\n  renderExtraRow?: (props: {\n    activity: WeekdayHourlyActivityWithLevel;\n    weekdayIndex: number;\n  }) => ReactNode;\n  renderExtraColumn?: (props: {\n    activity: WeekdayHourlyActivityWithLevel;\n    weekdayIndex: number;\n  }) => ReactNode;\n};\n\nexport const WeekdayHeatmapBody = ({\n  hideHourLabels = false,\n  hideWeekdayLabels = false,\n  className,\n  labelClassName,\n  children,\n  renderExtraRow,\n  renderExtraColumn,\n  ...props\n}: WeekdayHeatmapBodyProps) => {\n  const {\n    data,\n    extraRow,\n    extraColumn,\n    blockSize,\n    blockWidth,\n    blockMargin,\n    labels,\n    labelWidth,\n    labelHeight,\n    fontSize,\n    weekStart,\n  } = useWeekdayHeatmap();\n\n  const hasExtraRow = extraRow !== null;\n  const hasExtraColumn = extraColumn !== null;\n\n  const activityMap = useMemo(() => {\n    const map = new Map<string, WeekdayHourlyActivityWithLevel>();\n    data.forEach((activity) => {\n      map.set(`${activity.weekday}-${activity.hour}`, activity);\n    });\n    return map;\n  }, [data]);\n\n  const extraColumnMap = useMemo(() => {\n    if (!extraColumn) return null;\n    const map = new Map<\n      number,\n      { weekday: number; value: number; level: number }\n    >();\n    extraColumn.values.forEach((v) => {\n      map.set(v.weekday, v);\n    });\n    return map;\n  }, [extraColumn]);\n\n  const regularActivities = useMemo(() => {\n    const activities: {\n      activity: WeekdayHourlyActivityWithLevel;\n      weekdayIndex: number;\n    }[] = [];\n    for (let di = 0; di < 7; di++) {\n      const weekday = (weekStart + di) % 7;\n      for (let hour = 0; hour < 24; hour++) {\n        activities.push({\n          activity: activityMap.get(`${weekday}-${hour}`) ?? {\n            weekday,\n            hour,\n            value: 0,\n            level: 0,\n          },\n          weekdayIndex: di,\n        });\n      }\n    }\n    return activities;\n  }, [activityMap, weekStart]);\n\n  const extraColumnActivities = useMemo(() => {\n    if (!extraColumnMap) return [];\n    return Array.from({ length: 7 }, (_, di) => {\n      const weekday = (weekStart + di) % 7;\n      const entry = extraColumnMap.get(weekday);\n      return entry\n        ? {\n            activity: {\n              weekday,\n              hour: 0,\n              value: entry.value,\n              level: entry.level,\n            },\n            weekdayIndex: di,\n          }\n        : null;\n    }).filter(\n      (\n        a,\n      ): a is {\n        activity: WeekdayHourlyActivityWithLevel;\n        weekdayIndex: number;\n      } => a !== null,\n    );\n  }, [extraColumnMap, weekStart]);\n\n  const extraRowActivities = useMemo(() => {\n    if (!extraRow) return [];\n    return extraRow.values.map(({ hour, value, level }) => ({\n      activity: { weekday: 0, hour, value, level },\n      weekdayIndex: 7,\n    }));\n  }, [extraRow]);\n\n  const rowCount = hasExtraRow ? 8 : 7;\n  const extraRowGap = hasExtraRow ? blockSize + blockMargin : 0;\n  const svgHeight =\n    rowCount * (blockSize + blockMargin) +\n    extraRowGap -\n    blockMargin +\n    labelHeight;\n  const extraColumnGap = hasExtraColumn ? blockWidth + blockMargin : 0;\n  const svgWidth = hasExtraColumn\n    ? labelWidth +\n      24 * (blockWidth + blockMargin) +\n      blockSize / 2 +\n      (blockWidth + blockMargin) +\n      extraColumnGap\n    : labelWidth + 24 * (blockWidth + blockMargin) - blockMargin;\n\n  const orderedWeekdayIndices = Array.from(\n    { length: 7 },\n    (_, i) => (weekStart + i) % 7,\n  );\n\n  return (\n    <div\n      data-slot=\"weekday-heatmap-body\"\n      className={cn(\"max-w-full overflow-x-auto overflow-y-hidden\", className)}\n      {...props}\n    >\n      <svg\n        role=\"img\"\n        aria-label={\n          labels.heatmapLabel ?? \"Activity heatmap by weekday and hour\"\n        }\n        className=\"block overflow-visible rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n        height={svgHeight + PADDING * 2}\n        viewBox={`${-PADDING} ${-PADDING} ${svgWidth + PADDING * 2} ${svgHeight + PADDING * 2}`}\n        width={svgWidth + PADDING * 2}\n      >\n        {!hideHourLabels && (\n          <g className={cn(\"fill-current font-mono\", labelClassName)}>\n            {HOURS.map((hour) => {\n              const label = labels.hours?.[hour] ?? \"\";\n              return (\n                <text\n                  key={`hour-${hour}`}\n                  x={labelWidth + (blockWidth + blockMargin) * hour}\n                  y={0}\n                  textAnchor=\"middle\"\n                  dominantBaseline=\"hanging\"\n                  style={{ fontSize: `${fontSize * 0.75}px` }}\n                >\n                  {label}\n                </text>\n              );\n            })}\n            {labels.endHour != null && (\n              <text\n                key=\"hour-end\"\n                x={labelWidth + (blockWidth + blockMargin) * 24}\n                y={0}\n                textAnchor=\"middle\"\n                dominantBaseline=\"hanging\"\n                style={{ fontSize: `${fontSize * 0.75}px` }}\n              >\n                {labels.endHour}\n              </text>\n            )}\n            {hasExtraColumn && (\n              <text\n                key=\"extra-col-label\"\n                x={\n                  labelWidth +\n                  24 * (blockWidth + blockMargin) +\n                  blockSize / 2 +\n                  (blockWidth + blockMargin) +\n                  blockWidth / 2\n                }\n                y={0}\n                textAnchor=\"middle\"\n                dominantBaseline=\"hanging\"\n                style={{ fontSize: `${fontSize * 0.75}px` }}\n              >\n                {extraColumn?.label}\n              </text>\n            )}\n          </g>\n        )}\n\n        {!hideWeekdayLabels && (\n          <g className={cn(\"fill-current font-mono\", labelClassName)}>\n            {orderedWeekdayIndices.map((weekday, displayIndex) => {\n              const label = labels.weekdays?.[weekday] ?? \"\";\n              const yPosition =\n                labelHeight +\n                (blockSize + blockMargin) * displayIndex +\n                blockSize / 2;\n              return (\n                <text\n                  key={`weekday-${weekday}`}\n                  x={labelWidth - 8}\n                  y={yPosition}\n                  dominantBaseline=\"middle\"\n                  textAnchor=\"end\"\n                  style={{ fontSize: `${fontSize * 0.75}px` }}\n                >\n                  {label}\n                </text>\n              );\n            })}\n            {hasExtraRow && (\n              <text\n                key=\"weekday-extra\"\n                x={labelWidth - 8}\n                y={\n                  labelHeight +\n                  (blockSize + blockMargin) * 7 +\n                  (blockSize + blockMargin) +\n                  blockSize / 2\n                }\n                dominantBaseline=\"middle\"\n                textAnchor=\"end\"\n                style={{ fontSize: `${fontSize * 0.75}px` }}\n              >\n                {extraRow?.label}\n              </text>\n            )}\n          </g>\n        )}\n\n        {regularActivities.map(({ activity, weekdayIndex }) => (\n          <Fragment key={`${activity.weekday}-${activity.hour}`}>\n            {children({ activity, weekdayIndex })}\n          </Fragment>\n        ))}\n        {extraColumnActivities.map(({ activity, weekdayIndex }) => (\n          <Fragment key={`extra-column-${activity.weekday}`}>\n            {(renderExtraColumn ?? children)({ activity, weekdayIndex })}\n          </Fragment>\n        ))}\n        {extraRowActivities.map(({ activity, weekdayIndex }) => (\n          <Fragment key={`extra-row-${activity.hour}`}>\n            {(renderExtraRow ?? children)({ activity, weekdayIndex })}\n          </Fragment>\n        ))}\n      </svg>\n    </div>\n  );\n};\n\nexport type WeekdayHeatmapFooterProps = HTMLAttributes<HTMLDivElement>;\n\nexport const WeekdayHeatmapFooter = ({\n  className,\n  ...props\n}: WeekdayHeatmapFooterProps) => (\n  <div\n    data-slot=\"weekday-heatmap-footer\"\n    className={cn(\n      \"flex flex-wrap gap-1 whitespace-nowrap sm:gap-x-4\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport type WeekdayHeatmapStatProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  compute?: (data: WeekdayHourlyActivityWithLevel[]) => number | string;\n  label?: string; // Template overriding labels.stat. Placeholder: {{value}}\n  children?: (result: {\n    value: number | string;\n    data: WeekdayHourlyActivityWithLevel[];\n  }) => ReactNode;\n};\n\nexport const WeekdayHeatmapStat = ({\n  compute,\n  label,\n  className,\n  children,\n  ...props\n}: WeekdayHeatmapStatProps) => {\n  const { data, totalCount, labels } = useWeekdayHeatmap();\n\n  const value = compute ? compute(data) : totalCount;\n\n  if (children) {\n    return <>{children({ value, data })}</>;\n  }\n\n  const template = label ?? labels.stat ?? \"{{value}} contributions\";\n\n  return (\n    <div\n      data-slot=\"weekday-heatmap-stat\"\n      className={cn(\"text-muted-foreground tabular-nums\", className)}\n      {...props}\n    >\n      {template.replace(\"{{value}}\", String(value))}\n    </div>\n  );\n};\n\nexport type WeekdayHeatmapLegendProps = Omit<\n  HTMLAttributes<HTMLFieldSetElement>,\n  \"children\"\n> & {\n  labels?: { less?: string; more?: string };\n  children?: (props: { level: number }) => ReactNode;\n};\n\nexport const WeekdayHeatmapLegend = ({\n  labels: labelsProp,\n  className,\n  children,\n  ...props\n}: WeekdayHeatmapLegendProps) => {\n  const {\n    levels,\n    isNormalized,\n    blockSize,\n    blockWidth,\n    blockRadius,\n    colors,\n    labels,\n  } = useWeekdayHeatmap();\n\n  const lessLabel = labelsProp?.less ?? \"Less\";\n  const moreLabel = labelsProp?.more ?? \"More\";\n\n  const legendLevels = Array.from({ length: levels }, (_, i) =>\n    isNormalized ? i + 1 : i,\n  );\n\n  return (\n    <fieldset\n      data-slot=\"weekday-heatmap-legend\"\n      aria-label={labels.legendLabel ?? \"Activity intensity legend\"}\n      className={cn(\n        \"ml-auto flex items-center gap-1 text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    >\n      <span className=\"mr-1 font-medium text-xs\">{lessLabel}</span>\n      {legendLevels.map((level) =>\n        children ? (\n          <Fragment key={`legend-level-${level}`}>\n            {children({ level })}\n          </Fragment>\n        ) : (\n          <svg\n            role=\"img\"\n            aria-label={(\n              labels.legendLevelLabel ?? \"{{level}} contributions\"\n            ).replace(\"{{level}}\", String(level))}\n            height={blockSize}\n            key={`legend-level-${level}`}\n            width={blockWidth}\n          >\n            <rect\n              data-level={level}\n              height={blockSize}\n              rx={blockRadius}\n              ry={blockRadius}\n              width={blockWidth}\n              style={{\n                fill: getLevelFill(level, levels, isNormalized, false, colors),\n              }}\n            />\n          </svg>\n        ),\n      )}\n      <span className=\"ml-1 font-medium text-xs\">{moreLabel}</span>\n    </fieldset>\n  );\n};\n"
    }
  ],
  "cssVars": {
    "theme": {
      "secondary": "oklch(96.7% 0.001 286.4)",
      "chart-1": "oklch(64.6% 0.222 41.1)",
      "muted-foreground": "oklch(55.2% 0.014 285.9)"
    }
  }
}
