diff --git a/public/stars.json b/public/stars.json
new file mode 100644
index 00000000..bc1f4a8e
--- /dev/null
+++ b/public/stars.json
@@ -0,0 +1 @@
+9718
\ No newline at end of file
diff --git a/src/components/HomebrewBadge.tsx b/src/components/HomebrewBadge.tsx
new file mode 100644
index 00000000..c76155af
--- /dev/null
+++ b/src/components/HomebrewBadge.tsx
@@ -0,0 +1,57 @@
+import { Box, Tooltip, useTheme, useMediaQuery } from "@mui/material";
+
+/**
+ * HomebrewBadge — lightweight badge indicating pkgx's Homebrew heritage.
+ * Uses a styled Box instead of MUI Chip to minimize bundle impact.
+ *
+ * Accessibility: focusable, tooltip, role="status", aria-label.
+ * Responsive: smaller text on mobile.
+ */
+export default function HomebrewBadge() {
+ const theme = useTheme();
+ const isxs = useMediaQuery(theme.breakpoints.down("md"));
+
+ return (
+
+
+
+ 🍺
+
+ {isxs ? "By Homebrew's creator" : "From the creator of Homebrew"}
+
+
+ );
+}
diff --git a/src/components/Stars.tsx b/src/components/Stars.tsx
index eba55fb2..753c1ca8 100644
--- a/src/components/Stars.tsx
+++ b/src/components/Stars.tsx
@@ -1,27 +1,111 @@
import { Stack, IconButton, Box, Tooltip, Typography, useTheme, useMediaQuery } from "@mui/material";
import { useAsync } from "react-use";
+import { useState, useEffect, useRef, useCallback } from "react";
import github from "../assets/wordmarks/github.svg";
-export default function Stars({ href, hideCountIfMobile }: { href?: string, hideCountIfMobile?: boolean }) {
+/**
+ * Animated star counter that counts from 0 to the actual value.
+ * Uses requestAnimationFrame for smooth 60fps animation.
+ * Respects prefers-reduced-motion: skips animation and shows final value instantly.
+ */
+function useAnimatedCounter(target: number | undefined, durationMs = 1600): string {
+ const [display, setDisplay] = useState("");
+ const prefersReducedMotion = useRef(false);
+
+ useEffect(() => {
+ if (typeof window !== "undefined") {
+ prefersReducedMotion.current = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+ }
+ }, []);
+
+ const formatNumber = useCallback((n: number): string => {
+ return n.toLocaleString("en-US");
+ }, []);
+
+ useEffect(() => {
+ if (target === undefined || target === null) {
+ setDisplay("");
+ return;
+ }
+
+ // Respect reduced motion preference
+ if (prefersReducedMotion.current) {
+ setDisplay(formatNumber(target));
+ return;
+ }
+
+ let rafId: number;
+ let startTime: number | null = null;
+ const startValue = 0;
+
+ const animate = (timestamp: number) => {
+ if (startTime === null) startTime = timestamp;
+ const elapsed = timestamp - startTime;
+ const progress = Math.min(elapsed / durationMs, 1);
+
+ // Ease-out cubic for satisfying deceleration
+ const eased = 1 - Math.pow(1 - progress, 3);
+ const current = Math.round(startValue + (target - startValue) * eased);
+
+ setDisplay(formatNumber(current));
+
+ if (progress < 1) {
+ rafId = requestAnimationFrame(animate);
+ }
+ };
+
+ rafId = requestAnimationFrame(animate);
+
+ return () => {
+ if (rafId) cancelAnimationFrame(rafId);
+ };
+ }, [target, durationMs, formatNumber]);
+
+ return display;
+}
+
+export default function Stars({ href, hideCountIfMobile }: { href?: string; hideCountIfMobile?: boolean }) {
const theme = useTheme();
- const isxs = useMediaQuery(theme.breakpoints.down('md'));
+ const isxs = useMediaQuery(theme.breakpoints.down("md"));
- const {value: stars} = useAsync(async () => {
- const response = await fetch('/stars.json');
+ const { value: stars } = useAsync(async () => {
+ const response = await fetch("/stars.json");
const data = await response.json();
- return data
- }, [])
-
- const display = hideCountIfMobile && isxs ? 'none' : undefined
-
- return
-
-
-
-
-
- {stars}
-
-
-
-}
\ No newline at end of file
+ // Handle both number and string formats
+ const raw = typeof data === "object" && data !== null ? (data.total ?? data.stars ?? data) : data;
+ return typeof raw === "string" ? parseInt(raw.replace(/,/g, ""), 10) : Number(raw);
+ }, []);
+
+ const animatedStars = useAnimatedCounter(stars);
+ const shouldHide = hideCountIfMobile && isxs;
+
+ return (
+
+
+
+
+ {!shouldHide && (
+
+
+ {animatedStars || "\u00A0"}
+
+
+ )}
+
+ );
+}
diff --git a/src/pkgx.sh/Hero.tsx b/src/pkgx.sh/Hero.tsx
index 8c3a0cd8..57c3b163 100644
--- a/src/pkgx.sh/Hero.tsx
+++ b/src/pkgx.sh/Hero.tsx
@@ -3,6 +3,7 @@ import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import React, { useState } from "react";
import HeroTypography from '../components/HeroTypography'
+import HomebrewBadge from '../components/HomebrewBadge'
import { useSearchParams } from 'react-router-dom'
export default function Hero() {
@@ -28,6 +29,7 @@ export default function Hero() {
: undefined
return
+
Run Anything