{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shieldcn",
  "title": "shieldcn — Everything for your README",
  "description": "remocn demo composition \"shieldcn — Everything for your README\" — installs the full Remotion composition. Generated with AI from the prompt in demos/shieldcn/prompt.md.",
  "dependencies": [
    "@paper-design/shaders-react",
    "@remotion/google-fonts",
    "@remotion/transitions",
    "culori",
    "date-fns",
    "lucide-react",
    "remotion"
  ],
  "registryDependencies": [
    "https://remocn.dev/r/blur-in.json",
    "https://remocn.dev/r/github-stars.json",
    "https://remocn.dev/r/number-wheel.json",
    "https://remocn.dev/r/remocn-ui.json",
    "https://remocn.dev/r/shader-grain-gradient.json",
    "https://remocn.dev/r/short-slide-right.json",
    "https://remocn.dev/r/terminal-simulator.json"
  ],
  "files": [
    {
      "path": "src/demos/shieldcn/index.tsx",
      "content": "import React, { type ReactNode } from \"react\";\nimport { AbsoluteFill, Easing, Img, Sequence, Series, interpolate, interpolateColors, spring, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport { demoAsset } from \"@/lib/demo-assets\";\nimport {\n  TransitionSeries,\n  linearTiming,\n  type TransitionPresentation,\n  type TransitionPresentationComponentProps,\n} from \"@remotion/transitions\";\nimport { loadFont as loadSans } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadMono } from \"@remotion/google-fonts/GeistMono\";\n\nimport { RemocnUIProvider } from \"@/lib/remocn-ui\";\nimport { ShortSlideRight } from \"@/components/remocn/short-slide-right\";\nimport { TerminalSimulator } from \"@/components/remocn/terminal-simulator\";\nimport {\n  GitHubStars,\n  type Stargazer,\n} from \"@/components/remocn/github-stars\";\nimport { BlurIn } from \"@/components/remocn/blur-in\";\nimport { useBlurInTransition } from \"@/components/remocn/use-blur-in-transition\";\nimport { ShaderGrainGradient } from \"@/components/remocn/shader-grain-gradient\";\nimport { StarIcon } from \"lucide-react\";\n\n// shieldcn speaks shadcn's language: Geist Sans for copy, Geist Mono for every\n// URL and command.\nconst { fontFamily: SANS_FAMILY } = loadSans(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\", \"500\", \"600\", \"700\"],\n});\nconst { fontFamily: MONO_FAMILY } = loadMono(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\", \"500\", \"700\"],\n});\n\nconst SANS =\n  \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\";\nconst MONO = \"var(--font-geist-mono), ui-monospace, SFMono-Regular, monospace\";\n\n// shieldcn palette — the shadcn zinc register plus the one liberty the brand\n// takes: badge-value green. The dated shields colors live ONLY in the pain beat.\nconst BG = \"#09090b\"; //      zinc-950 canvas\nconst INK = \"#fafafa\"; //     zinc-50 text\nconst MUTED = \"#a1a1aa\"; //   zinc-400\nconst FAINT = \"rgba(250,250,250,0.45)\";\nconst BORDER = \"rgba(255,255,255,0.10)\";\nconst CARD = \"rgba(255,255,255,0.03)\";\nconst GREEN = \"#22c55e\"; //   the badge-value green\nconst GREEN_SOFT = \"#4ade80\";\n\n// ---------------------------------------------------------------------------\n// Scene timings (frames @ 30fps), one per beat. Transitions overlap.\n// ---------------------------------------------------------------------------\nconst S_HOOK = 120; //     stacked slide-in lines, README marker-highlighted\nconst S_PAIN = 120; //     counter-drifting marquee wall of dated badges\nconst S_INTRO = 185; //    Meet → shieldcn lockup → creed, on the shared Z axis\nconst S_TITLE = 55; //     interstitial section titles (short-slide-right)\nconst S_VARIANTS = 170; // ?variant= param morphs the live badge\nconst S_CHART = 140; //    spatial pan across chart / header / sponsor cards\nconst S_AGENT = 125; //    npx skills add jal-co/shieldcn\nconst S_PROOF = 120; //    stargazers card + proof pills\nconst S_CTA = 255; //      badge ballet (scatter → circle → spin → row → exit) + outro\n\nconst T_X = 14; //     crossfade\nconst T_SQ = 16; //    squeeze — snappy mechanical beat change\nconst T_ZOOM = 18; //  section turn (zoom-through)\nconst T_IRIS = 22; //  pill-shaped iris reveal\n\nexport const SHIELDCN_DURATION =\n  S_HOOK +\n  S_PAIN +\n  S_INTRO +\n  S_TITLE * 3 +\n  S_VARIANTS +\n  S_CHART +\n  S_AGENT +\n  S_PROOF +\n  S_CTA -\n  (T_SQ +\n    T_IRIS +\n    T_ZOOM +\n    T_SQ +\n    T_ZOOM +\n    T_SQ +\n    T_X +\n    T_ZOOM +\n    T_SQ +\n    T_IRIS);\n\n// ---------------------------------------------------------------------------\n// Reveal — blur-in wrapper driven by useBlurInTransition.\n// ---------------------------------------------------------------------------\nconst Reveal: React.FC<{\n  children: ReactNode;\n  delay?: number;\n  distance?: number;\n  blur?: number;\n  duration?: number;\n  display?: React.CSSProperties[\"display\"];\n}> = ({\n  children,\n  delay = 0,\n  distance = 16,\n  blur = 10,\n  duration = 20,\n  display = \"block\",\n}) => {\n  const style = useBlurInTransition(\n    [{ at: delay, state: \"revealed\", duration }],\n    { direction: \"up\", distance, blur },\n  );\n  return (\n    <BlurIn style={style} display={display}>\n      {children}\n    </BlurIn>\n  );\n};\n\n// ---------------------------------------------------------------------------\n// SlideLine — a short-slide-right line stacked inside a flex column.\n// SlideRich — the same motion for rich content (marker highlights etc.).\n// ---------------------------------------------------------------------------\nconst SlideLine: React.FC<{\n  text: string;\n  at: number;\n  fontSize: number;\n  color?: string;\n  fontWeight?: number;\n}> = ({ text, at, fontSize, color = INK, fontWeight = 600 }) => (\n  <div\n    style={{\n      position: \"relative\",\n      width: \"100%\",\n      height: Math.round(fontSize * 1.35),\n    }}\n  >\n    <Sequence from={at} layout=\"none\">\n      <ShortSlideRight\n        text={text}\n        fontSize={fontSize}\n        color={color}\n        fontWeight={fontWeight}\n      />\n    </Sequence>\n  </div>\n);\n\nconst SlideRich: React.FC<{ at: number; children: ReactNode }> = ({\n  at,\n  children,\n}) => {\n  const frame = useCurrentFrame();\n  const easing = Easing.bezier(0.2, 0.8, 0.2, 1);\n  const t = frame - at;\n  const x = interpolate(t, [0, 16], [-24, 0], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing,\n  });\n  const opacity = interpolate(t, [0, 10], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing,\n  });\n  const blurVal = interpolate(t, [0, 16], [1.2, 0], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing,\n  });\n  return (\n    <div\n      style={{\n        transform: `translateX(${x}px)`,\n        opacity,\n        filter: `blur(${blurVal}px)`,\n      }}\n    >\n      {children}\n    </div>\n  );\n};\n\n// ---------------------------------------------------------------------------\n// Mark — a green marker sweep behind a phrase; the text flips dark.\n// ---------------------------------------------------------------------------\nconst Mark: React.FC<{ children: string; startFrame?: number }> = ({\n  children,\n  startFrame = 8,\n}) => {\n  const frame = useCurrentFrame();\n  const { fps } = useVideoConfig();\n  const scaleX = spring({\n    frame: frame - startFrame,\n    fps,\n    config: { damping: 15, mass: 0.8 },\n  });\n  const textColor = interpolateColors(\n    interpolate(scaleX, [0.45, 0.85], [0, 1], {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n    }),\n    [0, 1],\n    [INK, \"#052e16\"],\n  );\n  return (\n    <span\n      style={{\n        position: \"relative\",\n        display: \"inline-block\",\n        whiteSpace: \"nowrap\",\n      }}\n    >\n      <span\n        aria-hidden\n        style={{\n          position: \"absolute\",\n          inset: \"-0.04em -0.14em\",\n          background: GREEN,\n          borderRadius: 8,\n          transformOrigin: \"left center\",\n          transform: `scaleX(${scaleX})`,\n          zIndex: 0,\n        }}\n      />\n      <span style={{ position: \"relative\", zIndex: 1, color: textColor }}>\n        {children}\n      </span>\n    </span>\n  );\n};\n\n// ---------------------------------------------------------------------------\n// SectionTitle — the block headline as its own interstitial beat: the line\n// slides in, then a green rule draws itself underneath.\n// ---------------------------------------------------------------------------\nconst SectionTitle: React.FC<{ text: string }> = ({ text }) => {\n  const frame = useCurrentFrame();\n  const off = interpolate(frame, [12, 30], [1, 0], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.inOut(Easing.cubic),\n  });\n  return (\n    <AbsoluteFill\n      style={{\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        flexDirection: \"column\",\n        gap: 18,\n      }}\n    >\n      <div style={{ position: \"relative\", width: \"100%\", height: 74 }}>\n        <ShortSlideRight\n          text={text}\n          fontSize={54}\n          color={INK}\n          fontWeight={600}\n        />\n      </div>\n      <svg width={190} height={6} viewBox=\"0 0 190 6\">\n        <path\n          d=\"M3 3 H187\"\n          pathLength={1}\n          strokeDasharray={1}\n          strokeDashoffset={off}\n          stroke={GREEN}\n          strokeWidth={3.5}\n          strokeLinecap=\"round\"\n          fill=\"none\"\n        />\n      </svg>\n    </AbsoluteFill>\n  );\n};\n\n// ---------------------------------------------------------------------------\n// SazSwap — shared-axis-z motion (same curves as the remocn component) for\n// arbitrary ReactNode content: outgoing scales up + fades, incoming scales in.\n// ---------------------------------------------------------------------------\nconst SazSwap: React.FC<{\n  from?: ReactNode;\n  to: ReactNode;\n  align?: \"center\" | \"start\";\n}> = ({ from, to, align = \"center\" }) => {\n  const frame = useCurrentFrame();\n  const exitDur = 11;\n  const enterDur = 16;\n  const newStart = Math.max(0, exitDur - 3 + 1);\n  const exitEasing = Easing.bezier(0.4, 0, 1, 1);\n  const enterEasing = Easing.bezier(0.2, 0, 0, 1);\n\n  const fromOpacity = interpolate(frame, [0, exitDur], [1, 0], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: exitEasing,\n  });\n  const fromScale = interpolate(frame, [0, exitDur], [1, 1.06], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: exitEasing,\n  });\n  const fromBlur = interpolate(frame, [0, exitDur], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: exitEasing,\n  });\n\n  const local = frame - newStart;\n  const toOpacity = interpolate(local, [0, enterDur], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: enterEasing,\n  });\n  const toScale = interpolate(local, [0, enterDur], [0.9, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: enterEasing,\n  });\n  const toBlur = interpolate(local, [0, enterDur], [2, 0], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: enterEasing,\n  });\n\n  // AbsoluteFill is a flex COLUMN — alignItems is the horizontal axis.\n  const center: React.CSSProperties = {\n    alignItems: align === \"center\" ? \"center\" : \"flex-start\",\n    justifyContent: \"center\",\n  };\n  return (\n    <AbsoluteFill>\n      {from && (\n        <AbsoluteFill\n          style={{\n            ...center,\n            opacity: fromOpacity,\n            transform: `scale(${fromScale})`,\n            filter: `blur(${fromBlur}px)`,\n          }}\n        >\n          {from}\n        </AbsoluteFill>\n      )}\n      <AbsoluteFill\n        style={{\n          ...center,\n          opacity: toOpacity,\n          transform: `scale(${toScale})`,\n          filter: `blur(${toBlur}px)`,\n        }}\n      >\n        {to}\n      </AbsoluteFill>\n    </AbsoluteFill>\n  );\n};\n\n// ---------------------------------------------------------------------------\n// Badge — a shieldcn badge rendered the way the product renders it: as a real\n// shadcn button. Split form carries a label segment and a green value segment.\n// ---------------------------------------------------------------------------\ntype BadgeVariant =\n  | \"default\"\n  | \"secondary\"\n  | \"outline\"\n  | \"ghost\"\n  | \"destructive\";\ntype BadgeSize = \"xs\" | \"sm\" | \"default\" | \"lg\";\n\nconst SIZE_STYLE: Record<\n  BadgeSize,\n  { height: number; fontSize: number; padX: number }\n> = {\n  xs: { height: 26, fontSize: 12.5, padX: 10 },\n  sm: { height: 32, fontSize: 14, padX: 13 },\n  default: { height: 38, fontSize: 15.5, padX: 16 },\n  lg: { height: 46, fontSize: 18, padX: 20 },\n};\n\nconst VARIANT_STYLE: Record<BadgeVariant, React.CSSProperties> = {\n  default: { background: INK, color: \"#09090b\" },\n  secondary: { background: \"#27272a\", color: INK },\n  outline: {\n    background: \"transparent\",\n    color: INK,\n    boxShadow: `inset 0 0 0 1px rgba(255,255,255,0.22)`,\n  },\n  ghost: { background: \"transparent\", color: MUTED },\n  destructive: { background: \"#dc2626\", color: INK },\n};\n\nconst Badge: React.FC<{\n  children: ReactNode;\n  variant?: BadgeVariant;\n  size?: BadgeSize;\n}> = ({ children, variant = \"secondary\", size = \"default\" }) => {\n  const s = SIZE_STYLE[size];\n  return (\n    <span\n      style={{\n        display: \"inline-flex\",\n        alignItems: \"center\",\n        gap: 7,\n        height: s.height,\n        padding: `0 ${s.padX}px`,\n        borderRadius: 8,\n        fontFamily: SANS,\n        fontWeight: 500,\n        fontSize: s.fontSize,\n        ...VARIANT_STYLE[variant],\n      }}\n    >\n      {children}\n    </span>\n  );\n};\n\n// ===========================================================================\n// Scene 1 — Hook. The lines accumulate this time, and \"Your README.\" gets the\n// green marker sweep.\n// ===========================================================================\nconst HookScene: React.FC = () => (\n  <AbsoluteFill\n    style={{\n      alignItems: \"center\",\n      justifyContent: \"center\",\n      flexDirection: \"column\",\n      gap: 8,\n    }}\n  >\n    <SlideLine\n      text=\"Someone just opened your repo.\"\n      at={0}\n      fontSize={38}\n      color={MUTED}\n      fontWeight={500}\n    />\n    <SlideLine\n      text=\"The first thing they see?\"\n      at={22}\n      fontSize={38}\n      color={MUTED}\n      fontWeight={500}\n    />\n    <div style={{ height: 8 }} />\n    <SlideRich at={44}>\n      <span\n        style={{\n          fontFamily: SANS,\n          fontWeight: 600,\n          fontSize: 56,\n          color: INK,\n        }}\n      >\n        <Mark startFrame={62}>Your README.</Mark>\n      </span>\n    </SlideRich>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 2 — The wall. REAL shieldcn badges (SVGs fetched from the repo's own\n// README) drift across the frame in two counter-scrolling bands — the tease\n// of what the front door could look like.\n// ===========================================================================\nconst RealBadge: React.FC<{ name: string; height?: number }> = ({\n  name,\n  height = 32,\n}) => (\n  <Img\n    src={demoAsset(`shieldcn/${name}.svg`)}\n    style={{ height, width: \"auto\", display: \"block\", flex: \"none\" }}\n  />\n);\n\nconst BADGE_ROW_1 = [\n  \"npm-react\",\n  \"stars-nextjs\",\n  \"views-shieldcn\",\n  \"dw-react\",\n  \"license-shieldcn\",\n  \"stars-shieldcn\",\n  \"last-commit\",\n  \"contributors\",\n];\n\nconst BADGE_ROW_2 = [\n  \"npm-react-secondary\",\n  \"npm-react-outline\",\n  \"vercel-oss\",\n  \"npm-typescript\",\n  \"stars-react\",\n  \"npm-react-destructive\",\n  \"npm-react-ghost\",\n  \"views-shieldcn\",\n];\n\nconst MarqueeRow: React.FC<{\n  items: string[];\n  pxPerFrame: number;\n  reverse?: boolean;\n}> = ({ items, pxPerFrame, reverse = false }) => {\n  const frame = useCurrentFrame();\n  const drift = frame * pxPerFrame;\n  const x = reverse ? -560 + drift : -drift;\n  const doubled = [...items, ...items];\n  return (\n    <div style={{ width: \"100%\", overflow: \"hidden\" }}>\n      <div\n        style={{\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: 12,\n          width: \"max-content\",\n          transform: `translateX(${x}px)`,\n        }}\n      >\n        {doubled.map((name, i) => (\n          <RealBadge key={`${name}-${i}`} name={name} />\n        ))}\n      </div>\n    </div>\n  );\n};\n\nconst TeaseScene: React.FC = () => (\n  <AbsoluteFill style={{ justifyContent: \"center\" }}>\n    <Reveal delay={2} distance={10} blur={8}>\n      <div\n        style={{\n          display: \"flex\",\n          flexDirection: \"column\",\n          gap: 12,\n          marginBottom: 44,\n        }}\n      >\n        <MarqueeRow items={BADGE_ROW_1} pxPerFrame={1.4} />\n        <MarqueeRow items={BADGE_ROW_2} pxPerFrame={1.1} reverse />\n      </div>\n    </Reveal>\n    <div\n      style={{\n        display: \"flex\",\n        flexDirection: \"column\",\n        alignItems: \"center\",\n        gap: 8,\n      }}\n    >\n      <SlideLine\n        text=\"Your README could look like this.\"\n        at={20}\n        fontSize={34}\n        color={MUTED}\n        fontWeight={500}\n      />\n      <SlideLine\n        text=\"A design system — all the way to the front door.\"\n        at={44}\n        fontSize={40}\n        color={INK}\n      />\n    </div>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 3 — Product intro. Three beats on the shared Z axis: Meet → the\n// shieldcn wordmark → the creed.\n// ===========================================================================\nconst Lockup: React.FC = () => (\n  <h1\n    style={{\n      margin: 0,\n      fontFamily: SANS,\n      fontWeight: 700,\n      fontSize: 92,\n      letterSpacing: \"-0.03em\",\n      color: INK,\n    }}\n  >\n    shieldcn\n  </h1>\n);\n\nconst MeetLabel = (\n  <span\n    style={{\n      fontFamily: SANS,\n      fontWeight: 600,\n      fontSize: 72,\n      letterSpacing: \"-0.03em\",\n      color: INK,\n    }}\n  >\n    Meet\n  </span>\n);\n\nconst Creed = (\n  <span\n    style={{\n      fontFamily: SANS,\n      fontWeight: 500,\n      fontSize: 27,\n      color: FAINT,\n      textAlign: \"center\",\n    }}\n  >\n    Badges, charts, headers —{\" \"}\n    <span style={{ color: INK, fontWeight: 600 }}>\n      as real shadcn/ui components.\n    </span>\n  </span>\n);\n\n// MicroPhase — the micro-scale-fade motion (same curve as the remocn\n// component: 0.96 → 1 scale + fade over 18f) for arbitrary content, plus a\n// gentle fade-out at the end of the phase so beats hand over cleanly.\nconst MicroPhase: React.FC<{ children: ReactNode; outAt?: number }> = ({\n  children,\n  outAt,\n}) => {\n  const frame = useCurrentFrame();\n  const easing = Easing.bezier(0.32, 0.72, 0, 1);\n  const opacityIn = interpolate(frame, [0, 18], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing,\n  });\n  const scale = interpolate(frame, [0, 18], [0.96, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing,\n  });\n  const opacityOut =\n    outAt === undefined\n      ? 1\n      : interpolate(frame, [outAt, outAt + 10], [1, 0], {\n          extrapolateLeft: \"clamp\",\n          extrapolateRight: \"clamp\",\n          easing: Easing.in(Easing.quad),\n        });\n  return (\n    <AbsoluteFill\n      style={{\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        opacity: opacityIn * opacityOut,\n        transform: `scale(${scale})`,\n      }}\n    >\n      {children}\n    </AbsoluteFill>\n  );\n};\n\nconst IntroScene: React.FC = () => (\n  <AbsoluteFill>\n    <Series>\n      <Series.Sequence durationInFrames={40} layout=\"none\">\n        <MicroPhase outAt={30}>{MeetLabel}</MicroPhase>\n      </Series.Sequence>\n      <Series.Sequence durationInFrames={75} layout=\"none\">\n        <MicroPhase outAt={63}>\n          <Lockup />\n        </MicroPhase>\n      </Series.Sequence>\n      <Series.Sequence durationInFrames={70} layout=\"none\">\n        <MicroPhase>{Creed}</MicroPhase>\n      </Series.Sequence>\n    </Series>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 4 — Make it yours. A split stage: the URL parameter swaps on the\n// left, and the SAME badge restyles live on the right — one param, any look.\n// The five variants then cascade in along the bottom.\n// ===========================================================================\nconst PARAM_VALUES: BadgeVariant[] = [\n  \"default\",\n  \"secondary\",\n  \"outline\",\n  \"destructive\",\n];\nconst PHASE_LEN = 30;\n\nconst ParamValue: React.FC<{ text: string }> = ({ text }) => (\n  <span\n    style={{\n      fontFamily: MONO,\n      fontWeight: 500,\n      fontSize: 26,\n      color: GREEN_SOFT,\n    }}\n  >\n    {text}\n  </span>\n);\n\nconst MorphBadge: React.FC = () => {\n  const frame = useCurrentFrame();\n  const idx = Math.min(\n    PARAM_VALUES.length - 1,\n    Math.max(0, Math.floor((frame - 12) / PHASE_LEN)),\n  );\n  const local = (frame - 12) % PHASE_LEN;\n  const pulse =\n    frame < 12\n      ? 1\n      : interpolate(local, [0, 7], [0.94, 1], {\n          extrapolateLeft: \"clamp\",\n          extrapolateRight: \"clamp\",\n          easing: Easing.bezier(0.34, 1.56, 0.64, 1),\n        });\n  // The icon follows the variant's text color, like a real shadcn button.\n  const iconColor = VARIANT_STYLE[PARAM_VALUES[idx]].color as string;\n  return (\n    <div style={{ transform: `scale(${1.55 * pulse})` }}>\n      <Badge variant={PARAM_VALUES[idx]} size=\"lg\">\n        <StarIcon size={18} color={iconColor} />\n        stars · 138k\n      </Badge>\n    </div>\n  );\n};\n\nconst VariantsScene: React.FC = () => (\n  <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"center\",\n        gap: 54,\n        marginBottom: 90,\n      }}\n    >\n      {/* Left — the URL, one param swapping */}\n      <div style={{ display: \"flex\", flexDirection: \"column\", gap: 14 }}>\n        <Reveal delay={2} distance={12} blur={8}>\n          <span style={{ fontFamily: MONO, fontSize: 20, color: FAINT }}>\n            shieldcn.dev/github/stars/….svg\n          </span>\n        </Reveal>\n        <Reveal delay={8} distance={12} blur={8}>\n          <div style={{ display: \"flex\", alignItems: \"center\" }}>\n            <span\n              style={{ fontFamily: MONO, fontSize: 26, color: INK }}\n            >\n              ?variant=\n            </span>\n            <div style={{ position: \"relative\", width: 240, height: 40 }}>\n              <Series>\n                <Series.Sequence durationInFrames={PHASE_LEN + 12} layout=\"none\">\n                  <SazSwap align=\"start\" to={<ParamValue text=\"default\" />} />\n                </Series.Sequence>\n                <Series.Sequence durationInFrames={PHASE_LEN} layout=\"none\">\n                  <SazSwap\n                    align=\"start\"\n                    from={<ParamValue text=\"default\" />}\n                    to={<ParamValue text=\"secondary\" />}\n                  />\n                </Series.Sequence>\n                <Series.Sequence durationInFrames={PHASE_LEN} layout=\"none\">\n                  <SazSwap\n                    align=\"start\"\n                    from={<ParamValue text=\"secondary\" />}\n                    to={<ParamValue text=\"outline\" />}\n                  />\n                </Series.Sequence>\n                <Series.Sequence durationInFrames={200} layout=\"none\">\n                  <SazSwap\n                    align=\"start\"\n                    from={<ParamValue text=\"outline\" />}\n                    to={<ParamValue text=\"destructive\" />}\n                  />\n                </Series.Sequence>\n              </Series>\n            </div>\n          </div>\n        </Reveal>\n      </div>\n\n      {/* Divider */}\n      <Reveal delay={6} distance={0} blur={6}>\n        <div style={{ width: 1, height: 130, background: BORDER }} />\n      </Reveal>\n\n      {/* Right — the same badge, restyled live */}\n      <div\n        style={{\n          width: 340,\n          display: \"flex\",\n          justifyContent: \"center\",\n        }}\n      >\n        <Reveal delay={6} distance={16} blur={10}>\n          <MorphBadge />\n        </Reveal>\n      </div>\n    </div>\n\n    {/* Bottom — the whole family cascades in */}\n    <div\n      style={{\n        position: \"absolute\",\n        bottom: 130,\n        display: \"flex\",\n        alignItems: \"center\",\n        gap: 12,\n      }}\n    >\n      {[\n        \"npm-react\",\n        \"npm-react-secondary\",\n        \"npm-react-outline\",\n        \"npm-react-ghost\",\n        \"npm-react-destructive\",\n      ].map((name, i) => (\n        <Reveal key={name} delay={116 + i * 4} distance={14} blur={9}>\n          <RealBadge name={name} height={30} />\n        </Reveal>\n      ))}\n      <Reveal delay={140} distance={10} blur={6}>\n        <span style={{ fontFamily: SANS, fontSize: 16, color: MUTED }}>\n          any size, any icon — dark mode built in\n        </span>\n      </Reveal>\n    </div>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 5 — More than badges. A spatial pan across three README artifacts —\n// the REAL rendered SVGs from the shieldcn repo: the star-history chart, the\n// header, the sponsor wall.\n// ===========================================================================\nconst CARD_W = 620;\nconst CARD_GAP = 48;\nconst CARD_STEP = CARD_W + CARD_GAP;\n\nconst ArtifactCard: React.FC<{\n  url: string;\n  children: ReactNode;\n  dim: number;\n}> = ({ url, children, dim }) => (\n  <div\n    style={{\n      width: CARD_W,\n      flex: \"none\",\n      borderRadius: 14,\n      border: `1px solid ${BORDER}`,\n      background: CARD,\n      overflow: \"hidden\",\n      opacity: 1 - 0.55 * dim,\n      transform: `scale(${1 - 0.06 * dim})`,\n    }}\n  >\n    <div\n      style={{\n        padding: \"12px 18px\",\n        borderBottom: `1px solid ${BORDER}`,\n        fontFamily: MONO,\n        fontSize: 13.5,\n        color: MUTED,\n        whiteSpace: \"nowrap\",\n      }}\n    >\n      {url}\n    </div>\n    <div\n      style={{\n        position: \"relative\",\n        width: CARD_W,\n        height: 300,\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n      }}\n    >\n      {children}\n    </div>\n  </div>\n);\n\nconst seg = (\n  frame: number,\n  from: number,\n  to: number,\n): number =>\n  interpolate(frame, [from, to], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.inOut(Easing.cubic),\n  });\n\nconst ChartScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const station = seg(frame, 42, 60) + seg(frame, 96, 114);\n  const x = -station * CARD_STEP;\n  const dimFor = (i: number) => Math.min(1, Math.abs(i - station));\n  return (\n    <AbsoluteFill style={{ justifyContent: \"center\" }}>\n      <Reveal delay={2} distance={30} blur={14} duration={22}>\n        <div\n          style={{\n            display: \"flex\",\n            gap: CARD_GAP,\n            alignItems: \"center\",\n            width: \"max-content\",\n            marginLeft: (1280 - CARD_W) / 2,\n            transform: `translateX(${x}px)`,\n          }}\n        >\n          {/* Card 1 — the real star-history chart */}\n          <ArtifactCard\n            url=\"shieldcn.dev/chart/github/stars/jal-co/shieldcn.svg\"\n            dim={dimFor(0)}\n          >\n            <Img\n              src={demoAsset(\"shieldcn/chart-stars.svg\")}\n              style={{ width: 596, height: \"auto\", display: \"block\" }}\n            />\n          </ArtifactCard>\n\n          {/* Card 2 — the real README header */}\n          <ArtifactCard url=\"shieldcn.dev/header/graph.svg?title=shieldcn\" dim={dimFor(1)}>\n            <Img\n              src={demoAsset(\"shieldcn/header-graph.svg\")}\n              style={{ width: 596, height: \"auto\", display: \"block\" }}\n            />\n          </ArtifactCard>\n\n          {/* Card 3 — the real sponsor wall */}\n          <ArtifactCard url=\"shieldcn.dev/sponsors/jal-co.svg\" dim={dimFor(2)}>\n            <Img\n              src={demoAsset(\"shieldcn/sponsors.svg\")}\n              style={{ width: 596, height: \"auto\", display: \"block\" }}\n            />\n          </ArtifactCard>\n        </div>\n      </Reveal>\n    </AbsoluteFill>\n  );\n};\n\n// ===========================================================================\n// Scene 6 — Your agent does it. The skill install, then the punchline.\n// ===========================================================================\nconst AgentScene: React.FC = () => (\n  <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n    <div style={{ width: 860 }}>\n      <TerminalSimulator\n        title=\"~/my-project\"\n        fontSize={20}\n        charsPerFrame={2}\n        chunkSize={2}\n        lines={[\n          {\n            text: \"npx skills add jal-co/shieldcn -a claude-code\",\n            type: \"command\",\n            delay: 0,\n          },\n          {\n            text: \"✓ Found skill shieldcn-badges\",\n            type: \"log\",\n            delay: 12,\n            pause: 8,\n          },\n          { text: \"✓ Installed for Claude Code\", type: \"success\", delay: 8 },\n          {\n            text: \"Your agent writes the README now.\",\n            type: \"log\",\n            delay: 12,\n          },\n        ]}\n      />\n    </div>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 7 — Built in the open. The real star card counts up while the proof\n// pills hold the top row.\n// ===========================================================================\nconst STARGAZERS: Stargazer[] = [\n  { login: \"junedev\", avatarUrl: \"\", starredAt: \"2025-09-14\" },\n  { login: \"mirak\", avatarUrl: \"\", starredAt: \"2025-10-02\" },\n  { login: \"tobiasw\", avatarUrl: \"\", starredAt: \"2025-11-18\" },\n  { login: \"sofia-oss\", avatarUrl: \"\", starredAt: \"2026-01-05\" },\n  { login: \"kentbuilds\", avatarUrl: \"\", starredAt: \"2026-02-21\" },\n  { login: \"annadev\", avatarUrl: \"\", starredAt: \"2026-04-09\" },\n  { login: \"dpetrov\", avatarUrl: \"\", starredAt: \"2026-05-27\" },\n  { login: \"clararw\", avatarUrl: \"\", starredAt: \"2026-06-30\" },\n];\n\nconst ProofScene: React.FC = () => (\n  <AbsoluteFill\n    style={{\n      alignItems: \"center\",\n      justifyContent: \"center\",\n      flexDirection: \"column\",\n      gap: 26,\n    }}\n  >\n    <div style={{ display: \"flex\", gap: 12 }}>\n      {[\"MIT license\", \"Vercel OSS Program\", \"shields.io alternative\"].map(\n        (chip, i) => (\n          <Reveal key={chip} delay={2 + i * 5} distance={14} blur={9}>\n            <Badge variant=\"outline\">{chip}</Badge>\n          </Reveal>\n        ),\n      )}\n    </div>\n    <Reveal delay={4} distance={24} blur={12} duration={14}>\n      {/* GitHubStars lays out for the full 1280×720 canvas — scale it down. */}\n      <div style={{ position: \"relative\", width: 1280 * 0.62, height: 720 * 0.62 }}>\n        <div\n          style={{\n            position: \"absolute\",\n            width: 1280,\n            height: 720,\n            transform: \"scale(0.62)\",\n            transformOrigin: \"top left\",\n          }}\n        >\n          <GitHubStars\n            repo=\"jal-co/shieldcn\"\n            totalStars={524}\n            stargazers={STARGAZERS}\n            accentColor={GREEN}\n            theme=\"dark\"\n            repoAvatarUrl=\"\"\n            speed={1}\n          />\n        </div>\n      </div>\n    </Reveal>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 8 — CTA. The badge ballet: ten real badges pop in at scattered spots,\n// gather into a circle, the circle makes one full turn, they fall into a row,\n// the row rides off-screen — and the outro lockup takes the stage.\n// ===========================================================================\nconst BALLET_BADGES = [\n  \"npm-react\",\n  \"stars-nextjs\",\n  \"views-shieldcn\",\n  \"license-shieldcn\",\n  \"stars-shieldcn\",\n  \"npm-react-secondary\",\n  \"npm-react-outline\",\n  \"npm-react-ghost\",\n  \"npm-react-destructive\",\n  \"npm-typescript\",\n];\n\n// Hand-placed \"random\" scatter (deterministic — no Math.random in Remotion).\nconst SCATTER: Array<[number, number]> = [\n  [-420, -180],\n  [300, -220],\n  [-140, -60],\n  [480, 60],\n  [-500, 120],\n  [120, -160],\n  [420, -40],\n  [-280, 220],\n  [60, 190],\n  [-40, -250],\n];\n\nconst BALLET_N = BALLET_BADGES.length;\nconst BALLET_R = 190; //  circle radius\nconst BALLET_SLOT = 116; // row slot width\n\n// Ballet timeline (scene-local frames)\nconst B_GATHER: [number, number] = [26, 48];\nconst B_SPIN: [number, number] = [48, 100];\nconst B_ALIGN: [number, number] = [100, 122];\nconst B_EXIT: [number, number] = [124, 144];\nconst OUTRO_FROM = 146;\n\nconst BadgeBallet: React.FC = () => {\n  const frame = useCurrentFrame();\n  const ease = Easing.inOut(Easing.cubic);\n  const gP = interpolate(frame, B_GATHER, [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: ease,\n  });\n  const rot = interpolate(frame, B_SPIN, [0, Math.PI * 2], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: ease,\n  });\n  const aP = interpolate(frame, B_ALIGN, [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: ease,\n  });\n  const eP = interpolate(frame, B_EXIT, [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.in(Easing.cubic),\n  });\n  return (\n    <>\n      {BALLET_BADGES.map((name, i) => {\n        const base = -Math.PI / 2 + (i * 2 * Math.PI) / BALLET_N;\n        const cx = Math.cos(base + rot) * BALLET_R;\n        const cy = Math.sin(base + rot) * BALLET_R;\n        const [sx, sy] = SCATTER[i];\n        // scatter → circle (the circle itself carries the spin) → row → exit\n        const gx = sx + (cx - sx) * gP;\n        const gy = sy + (cy - sy) * gP;\n        const rowX = (i - (BALLET_N - 1) / 2) * BALLET_SLOT;\n        const x = gx + (rowX - gx) * aP - eP * 1750;\n        const y = gy + (0 - gy) * aP;\n        const popIn = interpolate(frame, [i * 2, i * 2 + 8], [0, 1], {\n          extrapolateLeft: \"clamp\",\n          extrapolateRight: \"clamp\",\n        });\n        const popScale = interpolate(frame, [i * 2, i * 2 + 12], [0.5, 1], {\n          extrapolateLeft: \"clamp\",\n          extrapolateRight: \"clamp\",\n          easing: Easing.bezier(0.34, 1.56, 0.64, 1),\n        });\n        return (\n          <div\n            key={name}\n            style={{\n              position: \"absolute\",\n              left: \"50%\",\n              top: \"50%\",\n              transform: `translate(-50%, -50%) translate(${x}px, ${y}px) scale(${popScale})`,\n              opacity: popIn,\n            }}\n          >\n            <RealBadge name={name} height={26} />\n          </div>\n        );\n      })}\n    </>\n  );\n};\n\nconst CtaScene: React.FC = () => (\n  <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n    <BadgeBallet />\n    <Sequence from={OUTRO_FROM} layout=\"none\">\n      <div\n        style={{\n          display: \"flex\",\n          flexDirection: \"column\",\n          alignItems: \"center\",\n          gap: 22,\n        }}\n      >\n        <Reveal delay={10} distance={16} blur={14} duration={22}>\n          <div\n            style={{\n              fontFamily: SANS,\n              fontWeight: 700,\n              fontSize: 80,\n              letterSpacing: \"-0.03em\",\n              color: INK,\n            }}\n          >\n            shieldcn\n          </div>\n        </Reveal>\n        <Reveal delay={26} distance={12} blur={8}>\n          <span style={{ fontFamily: MONO, fontSize: 22, color: MUTED }}>\n            shieldcn.dev\n          </span>\n        </Reveal>\n        <Reveal delay={44} distance={10} blur={6}>\n          <span style={{ fontFamily: SANS, fontSize: 20, color: MUTED }}>\n            Make the first impression count.\n          </span>\n        </Reveal>\n      </div>\n    </Sequence>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Transition presentations.\n// ===========================================================================\ntype EmptyProps = Record<string, never>;\n\nconst Crossfade: React.FC<TransitionPresentationComponentProps<EmptyProps>> = ({\n  children,\n  presentationProgress,\n  presentationDirection,\n}) => {\n  const entering = presentationDirection === \"entering\";\n  const opacity = entering ? presentationProgress : 1 - presentationProgress;\n  return <AbsoluteFill style={{ opacity }}>{children}</AbsoluteFill>;\n};\nconst crossfade = (): TransitionPresentation<EmptyProps> => ({\n  component: Crossfade,\n  props: {},\n});\n\n// Squeeze — the outgoing frame compresses to a line at the top edge while the\n// incoming frame expands up from the bottom. Snappy, mechanical.\nconst SqueezePres: React.FC<\n  TransitionPresentationComponentProps<EmptyProps>\n> = ({ children, presentationProgress, presentationDirection }) => {\n  const entering = presentationDirection === \"entering\";\n  const p = interpolate(presentationProgress, [0, 1], [0, 1], {\n    easing: Easing.bezier(0.7, 0, 0.3, 1),\n  });\n  const style: React.CSSProperties = entering\n    ? {\n        transform: `scaleY(${Math.max(0.002, p)})`,\n        transformOrigin: \"50% 100%\",\n        opacity: Math.min(1, p * 2),\n      }\n    : {\n        transform: `scaleY(${Math.max(0.002, 1 - p)})`,\n        transformOrigin: \"50% 0%\",\n        opacity: Math.min(1, (1 - p) * 2),\n      };\n  return <AbsoluteFill style={style}>{children}</AbsoluteFill>;\n};\nconst squeeze = (): TransitionPresentation<EmptyProps> => ({\n  component: SqueezePres,\n  props: {},\n});\n\n// Iris — the incoming frame is revealed through an expanding badge-shaped\n// (pill) clip while the outgoing frame dims underneath.\nconst IrisPres: React.FC<TransitionPresentationComponentProps<EmptyProps>> = ({\n  children,\n  presentationProgress,\n  presentationDirection,\n}) => {\n  const entering = presentationDirection === \"entering\";\n  if (entering) {\n    const p = interpolate(presentationProgress, [0, 1], [0, 1], {\n      easing: Easing.out(Easing.cubic),\n    });\n    const vy = (1 - p) * 46;\n    const vx = (1 - p) * 38;\n    const r = (1 - p) * 380;\n    return (\n      <AbsoluteFill\n        style={{\n          clipPath: `inset(${vy}% ${vx}% ${vy}% ${vx}% round ${r}px)`,\n          transform: `scale(${1.04 - p * 0.04})`,\n        }}\n      >\n        {children}\n      </AbsoluteFill>\n    );\n  }\n  const p = presentationProgress;\n  return (\n    <AbsoluteFill style={{ opacity: 1 - p * 0.8, filter: `blur(${p * 6}px)` }}>\n      {children}\n    </AbsoluteFill>\n  );\n};\nconst iris = (): TransitionPresentation<EmptyProps> => ({\n  component: IrisPres,\n  props: {},\n});\n\nconst ZoomBlur: React.FC<\n  TransitionPresentationComponentProps<{ rise: number }>\n> = ({\n  children,\n  presentationProgress,\n  presentationDirection,\n  passedProps,\n}) => {\n  const { rise } = passedProps;\n  const entering = presentationDirection === \"entering\";\n  const p = interpolate(presentationProgress, [0, 1], [0, 1], {\n    easing: entering ? Easing.out(Easing.cubic) : Easing.in(Easing.cubic),\n  });\n  const style: React.CSSProperties = entering\n    ? {\n        opacity: p,\n        transform: `translateY(${(1 - p) * rise}px) scale(${0.86 + p * 0.14})`,\n        filter: p < 1 ? `blur(${(1 - p) * 18}px)` : undefined,\n      }\n    : {\n        opacity: 1 - p,\n        transform: `translateY(${-p * rise}px) scale(${1 + p * 0.18})`,\n        filter: p > 0 ? `blur(${p * 18}px)` : undefined,\n      };\n  return <AbsoluteFill style={style}>{children}</AbsoluteFill>;\n};\nconst zoomBlur = (rise = 0): TransitionPresentation<{ rise: number }> => ({\n  component: ZoomBlur,\n  props: { rise },\n});\n\n// ===========================================================================\n// Composition root.\n// ===========================================================================\nexport const ShieldcnDemo: React.FC = () => {\n  return (\n    <RemocnUIProvider>\n      <AbsoluteFill\n        style={\n          {\n            background: BG,\n            \"--font-geist-sans\": SANS_FAMILY,\n            \"--font-geist-mono\": MONO_FAMILY,\n          } as React.CSSProperties\n        }\n      >\n        {/* Living shader backdrop — grain gradient in the zinc register with a\n            whisper of the badge green. */}\n        <ShaderGrainGradient\n          speed={0.5}\n          colorBack={BG}\n          colors={[\"#101012\", \"#1c1c20\", \"#2a2a30\", \"#14352a\"]}\n        />\n        {/* Vignette scrim to focus the center. */}\n        <AbsoluteFill\n          style={{\n            background:\n              \"radial-gradient(120% 120% at 50% 45%, rgba(9,9,11,0) 30%, rgba(9,9,11,0.85) 100%)\",\n          }}\n        />\n\n        <TransitionSeries>\n          {/* 1 — Hook */}\n          <TransitionSeries.Sequence durationInFrames={S_HOOK}>\n            <HookScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_SQ })}\n            presentation={squeeze()}\n          />\n\n          {/* 2 — The wall of real badges */}\n          <TransitionSeries.Sequence durationInFrames={S_PAIN}>\n            <TeaseScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_IRIS })}\n            presentation={iris()}\n          />\n\n          {/* 3 — Intro: Meet → lockup → creed on the shared Z axis */}\n          <TransitionSeries.Sequence durationInFrames={S_INTRO}>\n            <IntroScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_ZOOM })}\n            presentation={zoomBlur(44)}\n          />\n\n          {/* — Section title: Make it yours */}\n          <TransitionSeries.Sequence durationInFrames={S_TITLE}>\n            <SectionTitle text=\"Make it yours\" />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_SQ })}\n            presentation={squeeze()}\n          />\n\n          {/* 4 — Variants: ?variant= morphs the live badge */}\n          <TransitionSeries.Sequence durationInFrames={S_VARIANTS}>\n            <VariantsScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_ZOOM })}\n            presentation={zoomBlur(44)}\n          />\n\n          {/* — Section title: More than badges */}\n          <TransitionSeries.Sequence durationInFrames={S_TITLE}>\n            <SectionTitle text=\"More than badges\" />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_SQ })}\n            presentation={squeeze()}\n          />\n\n          {/* 5 — Spatial pan across the three README artifacts */}\n          <TransitionSeries.Sequence durationInFrames={S_CHART}>\n            <ChartScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_X })}\n            presentation={crossfade()}\n          />\n\n          {/* 6 — Agent skill */}\n          <TransitionSeries.Sequence durationInFrames={S_AGENT}>\n            <AgentScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_ZOOM })}\n            presentation={zoomBlur(44)}\n          />\n\n          {/* — Section title: Built in the open */}\n          <TransitionSeries.Sequence durationInFrames={S_TITLE}>\n            <SectionTitle text=\"Built in the open\" />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_SQ })}\n            presentation={squeeze()}\n          />\n\n          {/* 7 — Proof: the star card counts up */}\n          <TransitionSeries.Sequence durationInFrames={S_PROOF}>\n            <ProofScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_IRIS })}\n            presentation={iris()}\n          />\n\n          {/* 8 — CTA: the stage clears, the lockup draws on */}\n          <TransitionSeries.Sequence durationInFrames={S_CTA}>\n            <CtaScene />\n          </TransitionSeries.Sequence>\n        </TransitionSeries>\n      </AbsoluteFill>\n    </RemocnUIProvider>\n  );\n};\n",
      "type": "registry:component",
      "target": "demos/shieldcn/index.tsx"
    },
    {
      "path": "src/lib/demo-assets.ts",
      "content": "import { staticFile } from \"remotion\";\n\n// Demo assets are loaded from absolute URLs so a demo installed into another\n// project via the registry renders identically without copying this repo's\n// public/ directory. The same URLs are used on the site and in local renders,\n// which keeps a single code path (network access is required for rendering).\n//\n// Override with REMOTION_DEMO_ASSETS_BASE — only REMOTION_-prefixed env vars\n// reach Remotion compositions (next.config.js additionally inlines it for the\n// site's <Player>). Two override forms:\n//   - \"local\" — serve straight from this repo's public/ via staticFile();\n//     works in Remotion Studio, CLI renders and the Next dev site. Set it in\n//     the gitignored .env so previews never depend on pushed assets.\n//   - any URL — e.g. a local static server (http://127.0.0.1:8123).\nexport const DEMO_ASSETS_BASE =\n  process.env.REMOTION_DEMO_ASSETS_BASE ||\n  \"https://raw.githubusercontent.com/Remocn/remocn-collections/main/public\";\n\nexport const demoAsset = (path: string): string => {\n  const clean = path.replace(/^\\/+/, \"\");\n  if (DEMO_ASSETS_BASE === \"local\") return staticFile(clean);\n  return `${DEMO_ASSETS_BASE}/${clean}`;\n};\n",
      "type": "registry:component",
      "target": "lib/demo-assets.ts"
    },
    {
      "path": "src/demos/shieldcn/prompt.md",
      "content": "<!-- TODO(draft): placeholder written by AI — replace with the real prompt used to generate this video -->\n\nMake a product demo for shieldcn — it's the shields.io alternative that turns README badges, charts, and headers into real shadcn/ui components. Use the shadcn zinc look plus their badge green as the accent, and run a before/after/bridge arc: open on \"someone just opened your repo\" and a wall of dated pixel badges as the pain, then introduce shieldcn with a self-drawing shield mark and the creed. Walk through the variants, sizes, and icons grid, show a live star-history chart along with header and sponsor-wall examples, and demo installing it as an agent skill in a terminal (npx skills add jal-co/shieldcn). Add proof pills for MIT and the Vercel OSS program plus a rolling star count past 500, and close on the shield-mark lockup with shieldcn.dev. Build it with remocn components.\n",
      "type": "registry:file",
      "target": "demos/shieldcn/prompt.md"
    }
  ],
  "docs": "Register the composition in your Remotion Root:\n\n  import { ShieldcnDemo } from \"@/demos/shieldcn\";\n  <Composition id=\"shieldcn\" component={ShieldcnDemo} durationInFrames={1224} fps={30} width={1280} height={720} />\n\nRequires Tailwind v4 wired into Remotion (@remotion/tailwind-v4).\nRender locally: npx remotion render shieldcn out/shieldcn.mp4 --gl=angle",
  "type": "registry:block"
}