{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "github-stars",
  "title": "GitHub Stars",
  "description": "Inertial fly-through of a repo's stargazers with a synced count-up odometer that locks on the total. Horizontal 16:9 or vertical 9:16.",
  "dependencies": ["remotion", "date-fns", "@remotion/google-fonts"],
  "registryDependencies": ["@remocn/number-wheel"],
  "files": [
    {
      "path": "registry/remocn/github-stars/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadMono } from \"@remotion/google-fonts/GeistMono\";\nimport { loadFont as loadSans } from \"@remotion/google-fonts/Manrope\";\nimport { format, parseISO } from \"date-fns\";\nimport { memo, useMemo, useState } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  Img,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\nimport { Odometer } from \"@/components/remocn/number-wheel\";\n\nexport interface Stargazer {\n  login: string;\n  avatarUrl: string;\n  /** ISO date string, e.g. \"2021-03-04\" */\n  starredAt: string;\n}\n\nexport interface GitHubStarsProps {\n  repo?: string;\n  totalStars?: number;\n  stargazers?: Stargazer[];\n  orientation?: \"horizontal\" | \"vertical\";\n  accentColor?: string;\n  speed?: number;\n  theme?: \"light\" | \"dark\";\n  repoAvatarUrl?: string;\n}\n\n/**\n * Built-in mock stargazers so the composition renders immediately from defaults\n * (own-your-code: no external data needed after `shadcn add`). Real low-id GitHub\n * avatar URLs are CORS-ok, so the docs preview shows round photos.\n */\nexport const SAMPLE_STARGAZERS: Stargazer[] = [\n  {\n    login: \"mojombo\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/1?v=4\",\n    starredAt: \"2021-03-04\",\n  },\n  {\n    login: \"defunkt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/2?v=4\",\n    starredAt: \"2021-06-12\",\n  },\n  {\n    login: \"pjhyett\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/3?v=4\",\n    starredAt: \"2021-09-21\",\n  },\n  {\n    login: \"wycats\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/4?v=4\",\n    starredAt: \"2021-12-08\",\n  },\n  {\n    login: \"ezmobius\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/14?v=4\",\n    starredAt: \"2022-03-17\",\n  },\n  {\n    login: \"ivey\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/18?v=4\",\n    starredAt: \"2022-06-29\",\n  },\n  {\n    login: \"evanphx\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/25?v=4\",\n    starredAt: \"2022-09-14\",\n  },\n  {\n    login: \"vanpelt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/26?v=4\",\n    starredAt: \"2022-12-23\",\n  },\n  {\n    login: \"wayneeseguin\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/28?v=4\",\n    starredAt: \"2023-03-09\",\n  },\n  {\n    login: \"brynary\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/30?v=4\",\n    starredAt: \"2023-07-19\",\n  },\n  {\n    login: \"kevinclark\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/31?v=4\",\n    starredAt: \"2023-11-02\",\n  },\n  {\n    login: \"technoweenie\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/21?v=4\",\n    starredAt: \"2024-02-15\",\n  },\n  {\n    login: \"mojombo\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/1?v=4\",\n    starredAt: \"2021-03-04\",\n  },\n  {\n    login: \"defunkt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/2?v=4\",\n    starredAt: \"2021-06-12\",\n  },\n  {\n    login: \"pjhyett\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/3?v=4\",\n    starredAt: \"2021-09-21\",\n  },\n  {\n    login: \"wycats\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/4?v=4\",\n    starredAt: \"2021-12-08\",\n  },\n  {\n    login: \"ezmobius\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/14?v=4\",\n    starredAt: \"2022-03-17\",\n  },\n  {\n    login: \"ivey\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/18?v=4\",\n    starredAt: \"2022-06-29\",\n  },\n  {\n    login: \"evanphx\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/25?v=4\",\n    starredAt: \"2022-09-14\",\n  },\n  {\n    login: \"vanpelt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/26?v=4\",\n    starredAt: \"2022-12-23\",\n  },\n  {\n    login: \"wayneeseguin\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/28?v=4\",\n    starredAt: \"2023-03-09\",\n  },\n  {\n    login: \"brynary\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/30?v=4\",\n    starredAt: \"2023-07-19\",\n  },\n  {\n    login: \"kevinclark\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/31?v=4\",\n    starredAt: \"2023-11-02\",\n  },\n  {\n    login: \"technoweenie\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/21?v=4\",\n    starredAt: \"2024-02-15\",\n  },\n  {\n    login: \"mojombo\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/1?v=4\",\n    starredAt: \"2021-03-04\",\n  },\n  {\n    login: \"defunkt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/2?v=4\",\n    starredAt: \"2021-06-12\",\n  },\n  {\n    login: \"pjhyett\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/3?v=4\",\n    starredAt: \"2021-09-21\",\n  },\n  {\n    login: \"wycats\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/4?v=4\",\n    starredAt: \"2021-12-08\",\n  },\n  {\n    login: \"ezmobius\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/14?v=4\",\n    starredAt: \"2022-03-17\",\n  },\n  {\n    login: \"ivey\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/18?v=4\",\n    starredAt: \"2022-06-29\",\n  },\n  {\n    login: \"evanphx\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/25?v=4\",\n    starredAt: \"2022-09-14\",\n  },\n  {\n    login: \"vanpelt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/26?v=4\",\n    starredAt: \"2022-12-23\",\n  },\n  {\n    login: \"wayneeseguin\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/28?v=4\",\n    starredAt: \"2023-03-09\",\n  },\n  {\n    login: \"brynary\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/30?v=4\",\n    starredAt: \"2023-07-19\",\n  },\n  {\n    login: \"kevinclark\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/31?v=4\",\n    starredAt: \"2023-11-02\",\n  },\n  {\n    login: \"technoweenie\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/21?v=4\",\n    starredAt: \"2024-02-15\",\n  },\n  {\n    login: \"mojombo\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/1?v=4\",\n    starredAt: \"2021-03-04\",\n  },\n  {\n    login: \"defunkt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/2?v=4\",\n    starredAt: \"2021-06-12\",\n  },\n  {\n    login: \"pjhyett\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/3?v=4\",\n    starredAt: \"2021-09-21\",\n  },\n  {\n    login: \"wycats\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/4?v=4\",\n    starredAt: \"2021-12-08\",\n  },\n  {\n    login: \"ezmobius\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/14?v=4\",\n    starredAt: \"2022-03-17\",\n  },\n  {\n    login: \"ivey\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/18?v=4\",\n    starredAt: \"2022-06-29\",\n  },\n  {\n    login: \"evanphx\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/25?v=4\",\n    starredAt: \"2022-09-14\",\n  },\n  {\n    login: \"vanpelt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/26?v=4\",\n    starredAt: \"2022-12-23\",\n  },\n  {\n    login: \"wayneeseguin\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/28?v=4\",\n    starredAt: \"2023-03-09\",\n  },\n  {\n    login: \"brynary\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/30?v=4\",\n    starredAt: \"2023-07-19\",\n  },\n  {\n    login: \"kevinclark\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/31?v=4\",\n    starredAt: \"2023-11-02\",\n  },\n  {\n    login: \"technoweenie\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/21?v=4\",\n    starredAt: \"2024-02-15\",\n  },\n  {\n    login: \"mojombo\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/1?v=4\",\n    starredAt: \"2021-03-04\",\n  },\n  {\n    login: \"defunkt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/2?v=4\",\n    starredAt: \"2021-06-12\",\n  },\n  {\n    login: \"pjhyett\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/3?v=4\",\n    starredAt: \"2021-09-21\",\n  },\n  {\n    login: \"wycats\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/4?v=4\",\n    starredAt: \"2021-12-08\",\n  },\n  {\n    login: \"ezmobius\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/14?v=4\",\n    starredAt: \"2022-03-17\",\n  },\n  {\n    login: \"ivey\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/18?v=4\",\n    starredAt: \"2022-06-29\",\n  },\n  {\n    login: \"evanphx\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/25?v=4\",\n    starredAt: \"2022-09-14\",\n  },\n  {\n    login: \"vanpelt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/26?v=4\",\n    starredAt: \"2022-12-23\",\n  },\n  {\n    login: \"wayneeseguin\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/28?v=4\",\n    starredAt: \"2023-03-09\",\n  },\n  {\n    login: \"brynary\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/30?v=4\",\n    starredAt: \"2023-07-19\",\n  },\n  {\n    login: \"kevinclark\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/31?v=4\",\n    starredAt: \"2023-11-02\",\n  },\n  {\n    login: \"technoweenie\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/21?v=4\",\n    starredAt: \"2024-02-15\",\n  },\n  {\n    login: \"mojombo\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/1?v=4\",\n    starredAt: \"2021-03-04\",\n  },\n  {\n    login: \"defunkt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/2?v=4\",\n    starredAt: \"2021-06-12\",\n  },\n  {\n    login: \"pjhyett\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/3?v=4\",\n    starredAt: \"2021-09-21\",\n  },\n  {\n    login: \"wycats\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/4?v=4\",\n    starredAt: \"2021-12-08\",\n  },\n  {\n    login: \"ezmobius\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/14?v=4\",\n    starredAt: \"2022-03-17\",\n  },\n  {\n    login: \"ivey\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/18?v=4\",\n    starredAt: \"2022-06-29\",\n  },\n  {\n    login: \"evanphx\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/25?v=4\",\n    starredAt: \"2022-09-14\",\n  },\n  {\n    login: \"vanpelt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/26?v=4\",\n    starredAt: \"2022-12-23\",\n  },\n  {\n    login: \"wayneeseguin\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/28?v=4\",\n    starredAt: \"2023-03-09\",\n  },\n  {\n    login: \"brynary\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/30?v=4\",\n    starredAt: \"2023-07-19\",\n  },\n  {\n    login: \"kevinclark\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/31?v=4\",\n    starredAt: \"2023-11-02\",\n  },\n  {\n    login: \"technoweenie\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/21?v=4\",\n    starredAt: \"2024-02-15\",\n  },\n  {\n    login: \"mojombo\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/1?v=4\",\n    starredAt: \"2021-03-04\",\n  },\n  {\n    login: \"defunkt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/2?v=4\",\n    starredAt: \"2021-06-12\",\n  },\n  {\n    login: \"pjhyett\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/3?v=4\",\n    starredAt: \"2021-09-21\",\n  },\n  {\n    login: \"wycats\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/4?v=4\",\n    starredAt: \"2021-12-08\",\n  },\n  {\n    login: \"ezmobius\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/14?v=4\",\n    starredAt: \"2022-03-17\",\n  },\n  {\n    login: \"ivey\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/18?v=4\",\n    starredAt: \"2022-06-29\",\n  },\n  {\n    login: \"evanphx\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/25?v=4\",\n    starredAt: \"2022-09-14\",\n  },\n  {\n    login: \"vanpelt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/26?v=4\",\n    starredAt: \"2022-12-23\",\n  },\n  {\n    login: \"wayneeseguin\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/28?v=4\",\n    starredAt: \"2023-03-09\",\n  },\n  {\n    login: \"brynary\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/30?v=4\",\n    starredAt: \"2023-07-19\",\n  },\n  {\n    login: \"kevinclark\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/31?v=4\",\n    starredAt: \"2023-11-02\",\n  },\n  {\n    login: \"technoweenie\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/21?v=4\",\n    starredAt: \"2024-02-15\",\n  },\n  {\n    login: \"mojombo\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/1?v=4\",\n    starredAt: \"2021-03-04\",\n  },\n  {\n    login: \"defunkt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/2?v=4\",\n    starredAt: \"2021-06-12\",\n  },\n  {\n    login: \"pjhyett\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/3?v=4\",\n    starredAt: \"2021-09-21\",\n  },\n  {\n    login: \"wycats\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/4?v=4\",\n    starredAt: \"2021-12-08\",\n  },\n  {\n    login: \"ezmobius\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/14?v=4\",\n    starredAt: \"2022-03-17\",\n  },\n  {\n    login: \"ivey\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/18?v=4\",\n    starredAt: \"2022-06-29\",\n  },\n  {\n    login: \"evanphx\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/25?v=4\",\n    starredAt: \"2022-09-14\",\n  },\n  {\n    login: \"vanpelt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/26?v=4\",\n    starredAt: \"2022-12-23\",\n  },\n  {\n    login: \"wayneeseguin\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/28?v=4\",\n    starredAt: \"2023-03-09\",\n  },\n  {\n    login: \"brynary\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/30?v=4\",\n    starredAt: \"2023-07-19\",\n  },\n  {\n    login: \"kevinclark\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/31?v=4\",\n    starredAt: \"2023-11-02\",\n  },\n  {\n    login: \"technoweenie\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/21?v=4\",\n    starredAt: \"2024-02-15\",\n  },\n  {\n    login: \"mojombo\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/1?v=4\",\n    starredAt: \"2021-03-04\",\n  },\n  {\n    login: \"defunkt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/2?v=4\",\n    starredAt: \"2021-06-12\",\n  },\n  {\n    login: \"pjhyett\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/3?v=4\",\n    starredAt: \"2021-09-21\",\n  },\n  {\n    login: \"wycats\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/4?v=4\",\n    starredAt: \"2021-12-08\",\n  },\n  {\n    login: \"ezmobius\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/14?v=4\",\n    starredAt: \"2022-03-17\",\n  },\n  {\n    login: \"ivey\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/18?v=4\",\n    starredAt: \"2022-06-29\",\n  },\n  {\n    login: \"evanphx\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/25?v=4\",\n    starredAt: \"2022-09-14\",\n  },\n  {\n    login: \"vanpelt\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/26?v=4\",\n    starredAt: \"2022-12-23\",\n  },\n  {\n    login: \"wayneeseguin\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/28?v=4\",\n    starredAt: \"2023-03-09\",\n  },\n  {\n    login: \"brynary\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/30?v=4\",\n    starredAt: \"2023-07-19\",\n  },\n  {\n    login: \"kevinclark\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/31?v=4\",\n    starredAt: \"2023-11-02\",\n  },\n  {\n    login: \"technoweenie\",\n    avatarUrl: \"https://avatars.githubusercontent.com/u/21?v=4\",\n    starredAt: \"2024-02-15\",\n  },\n];\n\n// Load fonts INSIDE the composition (via @remotion/google-fonts, which waits for\n// the font with delayRender before rendering frames). Server renders run in a\n// bare headless Chromium with no app CSS / `--font-*` vars, so without this the\n// MP4 falls back to the default sans-serif. Manrope for UI text, JetBrains Mono\n// for the numeric star counter (odometer).\nconst { fontFamily: SANS_FAMILY } = loadSans();\nconst { fontFamily: MONO_FAMILY } = loadMono();\n\nconst FONT_FAMILY = SANS_FAMILY;\n\ninterface Theme {\n  bg: string;\n  bgSubtle: string;\n  fg: string;\n  fgMuted: string;\n  border: string;\n}\n\nconst THEMES: Record<\"light\" | \"dark\", Theme> = {\n  light: {\n    bg: \"#ffffff\",\n    bgSubtle: \"#fafafa\",\n    fg: \"#171717\",\n    fgMuted: \"#737373\",\n    border: \"#ededed\",\n  },\n  dark: {\n    bg: \"#0a0a0a\",\n    bgSubtle: \"#111113\",\n    fg: \"#fafafa\",\n    fgMuted: \"#a1a1aa\",\n    border: \"#262626\",\n  },\n};\n\n// --- Pure helpers (unit-tested) -------------------------------------------\n\n/**\n * Clamp a stargazer list to at most `max` evenly-sampled keyframe rows while\n * always preserving the first and last entry. Safety net for callers that pass\n * a huge array straight into the composition.\n */\nexport function downsampleStargazers(\n  stargazers: Stargazer[],\n  max = 60,\n): Stargazer[] {\n  const len = stargazers.length;\n  if (len <= max) return stargazers;\n  if (max <= 1) return len ? [stargazers[0]] : [];\n  const out: Stargazer[] = [];\n  for (let i = 0; i < max; i++) {\n    out.push(stargazers[Math.round((i * (len - 1)) / (max - 1))]);\n  }\n  return out;\n}\n\nconst MAX_ELASTIC_OVERSHOOT = 0.0658; // Easing.elastic(1) peak−1 (true ≈0.0657267), rounded UP for use as a safety bound\nconst SCROLL_OVERSHOOT = 1 + MAX_ELASTIC_OVERSHOOT; // ≈1.0658\nconst MASK_SOLID_FRACTION = 0.88; // bottom fade mask is solid until 88% of viewport height\nconst COUNT_PORTION = 0.8;\n\n/** Monotonic counter ramp — mirrors the reference StarCount easing. Drives the\n *  odometer, which must stay monotonic or the digit wheels roll backward. */\nexport function computeCounterProgress({\n  frame,\n  speed = 1,\n  durationInFrames,\n}: {\n  frame: number;\n  speed?: number;\n  durationInFrames: number;\n}): number {\n  return interpolate(\n    frame * speed,\n    [0, durationInFrames * COUNT_PORTION],\n    [0, 1],\n    {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n      easing: Easing.bezier(0.5, 1, 0.5, 1),\n    },\n  );\n}\n\n/** Elastic scroll ramp — mirrors the reference avatar slide. NON-monotonic:\n *  overshoots to ~SCROLL_OVERSHOOT near t≈0.63 and settles to exactly 1 at t=1.\n *  Drives only scrollY (never the odometer). */\nexport function computeScrollProgress({\n  frame,\n  speed = 1,\n  durationInFrames,\n}: {\n  frame: number;\n  speed?: number;\n  durationInFrames: number;\n}): number {\n  return interpolate(frame * speed, [0, durationInFrames], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.elastic(0.8),\n  });\n}\n\n/** Empty rows appended below the last stargazer so the elastic recoil never\n *  lifts real content off the mask-solid zone, revealing a void. Single source\n *  of truth shared with isScrollContained so the two cannot drift. */\nexport function computeSpacerRows({\n  N,\n  rowH,\n  viewportH,\n  visibleRows,\n}: {\n  N: number;\n  rowH: number;\n  viewportH: number;\n  visibleRows: number;\n}): number {\n  const D = Math.max(0, (N - visibleRows + 1) * rowH);\n  const needPx =\n    MASK_SOLID_FRACTION * viewportH - N * rowH + SCROLL_OVERSHOOT * D;\n  return Math.max(0, Math.ceil(needPx / rowH));\n}\n\n/** True when, at the elastic overshoot peak, content still fills the mask-solid\n *  zone (no void revealed). True by construction given computeSpacerRows. */\nexport function isScrollContained({\n  N,\n  rowH,\n  viewportH,\n  visibleRows,\n}: {\n  N: number;\n  rowH: number;\n  viewportH: number;\n  visibleRows: number;\n}): boolean {\n  const D = Math.max(0, (N - visibleRows + 1) * rowH);\n  const spacerPx =\n    computeSpacerRows({ N, rowH, viewportH, visibleRows }) * rowH;\n  return (\n    N * rowH + spacerPx - SCROLL_OVERSHOOT * D >=\n    MASK_SOLID_FRACTION * viewportH\n  );\n}\n\n/** The running counter value for a given scroll progress. Locks at totalStars. */\nexport function getStarCount(\n  scrollProgress: number,\n  totalStars: number,\n): number {\n  return Math.round(scrollProgress * totalStars);\n}\n\n// --- Sub-components --------------------------------------------------------\n\nfunction StarGlyph({ size, fill }: { size: number; fill: string }) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      width={size}\n      height={size}\n      color={fill}\n      fill={fill}\n      stroke={fill}\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <title>Stars</title>\n      <path d=\"M13.7276 3.44418L15.4874 6.99288C15.7274 7.48687 16.3673 7.9607 16.9073 8.05143L20.0969 8.58575C22.1367 8.92853 22.6167 10.4206 21.1468 11.8925L18.6671 14.3927C18.2471 14.8161 18.0172 15.6327 18.1471 16.2175L18.8571 19.3125C19.417 21.7623 18.1271 22.71 15.9774 21.4296L12.9877 19.6452C12.4478 19.3226 11.5579 19.3226 11.0079 19.6452L8.01827 21.4296C5.8785 22.71 4.57865 21.7522 5.13859 19.3125L5.84851 16.2175C5.97849 15.6327 5.74852 14.8161 5.32856 14.3927L2.84884 11.8925C1.389 10.4206 1.85895 8.92853 3.89872 8.58575L7.08837 8.05143C7.61831 7.9607 8.25824 7.48687 8.49821 6.99288L10.258 3.44418C11.2179 1.51861 12.7777 1.51861 13.7276 3.44418Z\"></path>\n    </svg>\n  );\n}\n\nfunction Avatar({\n  login,\n  avatarUrl,\n  size,\n  theme,\n}: {\n  login: string;\n  avatarUrl: string;\n  size: number;\n  theme: Theme;\n}) {\n  const [errored, setErrored] = useState(false);\n  const ringStyle = {\n    width: size,\n    height: size,\n    borderRadius: 9999,\n    border: `1px solid ${theme.border}`,\n    flexShrink: 0,\n  } as const;\n\n  if (errored || !avatarUrl) {\n    return (\n      <div\n        style={{\n          ...ringStyle,\n          background: theme.bgSubtle,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          color: theme.fgMuted,\n          fontSize: size * 0.42,\n          fontWeight: 600,\n        }}\n      >\n        {login.charAt(0).toUpperCase()}\n      </div>\n    );\n  }\n\n  return (\n    <Img\n      src={avatarUrl}\n      crossOrigin=\"anonymous\"\n      onError={() => setErrored(true)}\n      style={{ ...ringStyle, objectFit: \"cover\" }}\n    />\n  );\n}\n\ninterface RowProps {\n  stargazer: Stargazer;\n  index: number;\n  rowH: number;\n  avatarSize: number;\n  loginSize: number;\n  dateLabel: string;\n  accent: string;\n  theme: Theme;\n  isActive: boolean;\n  settle: number;\n  showSeparator: boolean;\n  opacity: number;\n}\n\nconst Row = memo(function Row({\n  stargazer,\n  index,\n  rowH,\n  avatarSize,\n  loginSize,\n  dateLabel,\n  accent,\n  theme,\n  isActive,\n  settle,\n  showSeparator,\n  opacity,\n}: RowProps) {\n  return (\n    <div\n      style={{\n        position: \"relative\",\n        height: rowH,\n        display: \"flex\",\n        alignItems: \"center\",\n        paddingLeft: 24,\n        paddingRight: 24,\n        background: \"transparent\",\n        opacity,\n        boxSizing: \"border-box\",\n        borderBottom:\n          showSeparator && !isActive ? `1px solid ${theme.border}` : \"none\",\n      }}\n    >\n      {isActive && (\n        <>\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              background: theme.bgSubtle,\n              opacity: settle,\n            }}\n          />\n          <div\n            style={{\n              position: \"absolute\",\n              left: 0,\n              top: 0,\n              bottom: 0,\n              width: 2,\n              background: accent,\n              opacity: settle,\n            }}\n          />\n        </>\n      )}\n      <div\n        style={{\n          position: \"relative\",\n          display: \"flex\",\n          alignItems: \"center\",\n          width: \"100%\",\n        }}\n      >\n        <Avatar\n          login={stargazer.login}\n          avatarUrl={stargazer.avatarUrl}\n          size={avatarSize}\n          theme={theme}\n        />\n        <div style={{ marginLeft: 20, minWidth: 0, flex: 1 }}>\n          <div\n            style={{\n              fontSize: loginSize,\n              fontWeight: 600,\n              color: theme.fg,\n              fontFamily: FONT_FAMILY,\n              whiteSpace: \"nowrap\",\n              overflow: \"hidden\",\n              textOverflow: \"ellipsis\",\n              lineHeight: 1.2,\n            }}\n          >\n            {stargazer.login}\n          </div>\n          <div\n            style={{\n              fontSize: 18,\n              fontWeight: 400,\n              color: theme.fgMuted,\n              fontFamily: FONT_FAMILY,\n              lineHeight: 1.3,\n            }}\n          >\n            {dateLabel}\n          </div>\n        </div>\n        <div\n          style={{\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: 8,\n            color: theme.fgMuted,\n            fontFamily: MONO_FAMILY,\n            fontSize: 18,\n            fontVariantNumeric: \"tabular-nums\",\n            flexShrink: 0,\n          }}\n        >\n          <StarGlyph size={20} fill={accent} />\n          <span>#{index + 1}</span>\n        </div>\n      </div>\n    </div>\n  );\n});\n\n// --- Main composition ------------------------------------------------------\n\nexport function GitHubStars({\n  repo = \"remotion-dev/remotion\",\n  totalStars = 24813,\n  stargazers = SAMPLE_STARGAZERS,\n  orientation = \"horizontal\",\n  accentColor = \"#ffbb00\",\n  speed = 1,\n  theme = \"light\",\n  repoAvatarUrl,\n}: GitHubStarsProps) {\n  const frame = useCurrentFrame();\n  const { durationInFrames, width, height } = useVideoConfig();\n  const t = THEMES[theme] ?? THEMES.light;\n  const isVertical = orientation === \"vertical\";\n\n  // Render at the native reference size for the orientation, then scale to fit\n  // the actual canvas — keeps proportions at any resolution and letterboxes a\n  // vertical layout cleanly inside a horizontal preview canvas.\n  const refW = isVertical ? 720 : 1280;\n  const refH = isVertical ? 1280 : 720;\n  const stageScale = Math.min(width / refW, height / refH);\n\n  const rows = useMemo(() => downsampleStargazers(stargazers), [stargazers]);\n  const N = rows.length;\n\n  // Progress drivers: monotonic counter ramp + non-monotonic elastic scroll.\n  const counterProgress = computeCounterProgress({\n    frame,\n    speed,\n    durationInFrames,\n  });\n  const pScroll = computeScrollProgress({ frame, speed, durationInFrames });\n  const p = Math.max(0, Math.min(1, (frame * speed) / durationInFrames));\n\n  const current = counterProgress * totalStars;\n\n  // List viewport geometry (in reference px).\n  const rowH = isVertical ? 96 : 88;\n  const avatarSize = isVertical ? 60 : 56;\n  const loginSize = isVertical ? 30 : 28;\n  const dateFormat = isVertical ? \"MMM yyyy\" : \"MMM d, yyyy\";\n  const formattedRows = useMemo(\n    () =>\n      rows.map((sg) => {\n        try {\n          return { sg, dateLabel: format(parseISO(sg.starredAt), dateFormat) };\n        } catch {\n          return { sg, dateLabel: sg.starredAt };\n        }\n      }),\n    [rows, dateFormat],\n  );\n\n  const viewport = isVertical\n    ? { x: 48, y: 320, w: 624, h: 888, radius: 28 }\n    : { x: 560, y: 60, w: 640, h: 600, radius: 24 };\n\n  const visibleRows = Math.floor(viewport.h / rowH);\n  const D = Math.max(0, (N - visibleRows + 1) * rowH);\n  const scrollY = -pScroll * D;\n\n  const spacerRows = computeSpacerRows({\n    N,\n    rowH,\n    viewportH: viewport.h,\n    visibleRows,\n  });\n  const spacerPx = spacerRows * rowH;\n\n  const settle = interpolate(p, [0.92, 1], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n  });\n\n  // Counter zone type scale.\n  const counterSize = isVertical ? 96 : 120;\n  const starSize = isVertical ? 56 : 64;\n  const underlineMax = isVertical ? 200 : 240;\n  const underlineWidth = counterProgress * underlineMax;\n\n  const fadeH = Math.round(viewport.h * 0.12);\n\n  const listViewport = (\n    <div\n      style={{\n        position: \"absolute\",\n        left: viewport.x,\n        top: viewport.y,\n        width: viewport.w,\n        height: viewport.h,\n        overflow: \"hidden\",\n        borderRadius: viewport.radius,\n        border: `1px solid ${t.border}`,\n        background: t.bg,\n      }}\n    >\n      <div\n        style={{\n          transform: `translateY(${scrollY}px)`,\n          willChange: \"transform\",\n        }}\n      >\n        {formattedRows.map(({ sg, dateLabel }, i) => (\n          <Row\n            key={`${sg.login}-${i}`}\n            stargazer={sg}\n            index={i}\n            rowH={rowH}\n            avatarSize={avatarSize}\n            loginSize={loginSize}\n            dateLabel={dateLabel}\n            accent={accentColor}\n            theme={t}\n            isActive={i === N - 1}\n            settle={i === N - 1 ? settle : 0}\n            showSeparator={i < N - 1}\n            opacity={1}\n          />\n        ))}\n        {spacerPx > 0 && (\n          <div style={{ height: spacerPx }} aria-hidden=\"true\" />\n        )}\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          top: 0,\n          left: 0,\n          right: 0,\n          height: fadeH,\n          background: `linear-gradient(to bottom, ${t.bg}, transparent)`,\n          pointerEvents: \"none\",\n        }}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          bottom: 0,\n          left: 0,\n          right: 0,\n          height: fadeH,\n          background: `linear-gradient(to top, ${t.bg}, transparent)`,\n          pointerEvents: \"none\",\n        }}\n      />\n    </div>\n  );\n\n  const underline = (\n    <div\n      style={{\n        height: 4,\n        width: underlineWidth,\n        background: accentColor,\n        borderRadius: 2,\n      }}\n    />\n  );\n\n  const repoOwner = repo.split(\"/\")[0] || repo;\n  const repoAvatarSize = isVertical ? 28 : 32;\n  const repoAvatarSrc =\n    repoAvatarUrl ?? `https://unavatar.io/github/${repoOwner}`;\n\n  const repoSlug = (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"center\",\n        gap: 12,\n        maxWidth: isVertical ? 600 : 392,\n      }}\n    >\n      <Avatar\n        login={repoOwner}\n        avatarUrl={repoAvatarSrc}\n        size={repoAvatarSize}\n        theme={t}\n      />\n      <div\n        style={{\n          fontSize: isVertical ? 20 : 24,\n          fontWeight: 500,\n          color: t.fgMuted,\n          fontFamily: FONT_FAMILY,\n          overflow: \"hidden\",\n          textOverflow: \"ellipsis\",\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        {repo}\n      </div>\n    </div>\n  );\n\n  const counterZone = isVertical ? (\n    <div\n      style={{\n        position: \"absolute\",\n        left: 0,\n        top: 0,\n        width: refW,\n        height: 300,\n        paddingTop: 72,\n        display: \"flex\",\n        flexDirection: \"column\",\n        alignItems: \"center\",\n        gap: 14,\n      }}\n    >\n      <div style={{ display: \"flex\", alignItems: \"center\", gap: 20 }}>\n        <StarGlyph size={starSize} fill={accentColor} />\n        <Odometer current={current} fontSize={counterSize} color={t.fg} />\n      </div>\n      {underline}\n      {repoSlug}\n    </div>\n  ) : (\n    <div\n      style={{\n        position: \"absolute\",\n        left: 80,\n        top: 0,\n        width: 392,\n        height: refH,\n        display: \"flex\",\n        flexDirection: \"column\",\n        justifyContent: \"center\",\n        alignItems: \"flex-start\",\n      }}\n    >\n      <StarGlyph size={starSize} fill={accentColor} />\n      <div style={{ height: 20 }} />\n      <Odometer current={current} fontSize={counterSize} color={t.fg} />\n      <div style={{ height: 16 }} />\n      {underline}\n      <div style={{ height: 20 }} />\n\n      {repoSlug}\n    </div>\n  );\n\n  return (\n    <AbsoluteFill style={{ background: t.bg }}>\n      <div\n        style={{\n          position: \"absolute\",\n          left: \"50%\",\n          top: \"50%\",\n          width: refW,\n          height: refH,\n          transform: `translate(-50%, -50%) scale(${stageScale})`,\n        }}\n      >\n        {counterZone}\n        {listViewport}\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/github-stars.tsx"
    }
  ],
  "type": "registry:component"
}
