{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fonttrio",
  "title": "Fonttrio — Three fonts. One command.",
  "description": "remocn demo composition \"Fonttrio — Three fonts. One command.\" — installs the full Remotion composition. Generated with AI from the prompt in demos/fonttrio/prompt.md.",
  "dependencies": [
    "@remotion/google-fonts",
    "@remotion/transitions",
    "culori",
    "lucide-react",
    "remotion"
  ],
  "registryDependencies": [
    "https://remocn.dev/r/backdrop.json",
    "https://remocn.dev/r/blur-in.json",
    "https://remocn.dev/r/remocn-ui.json",
    "https://remocn.dev/r/rolling-number.json",
    "https://remocn.dev/r/shared-axis-z.json",
    "https://remocn.dev/r/terminal-cursor-zoom.json",
    "https://remocn.dev/r/terminal-simulator.json"
  ],
  "files": [
    {
      "path": "src/components/remocn/glass-code-block.tsx",
      "content": "\"use client\";\n\nimport {\n  Sequence,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport interface GlassCodeBlockProps {\n  code?: string;\n  title?: string;\n  width?: number;\n  height?: number;\n  fontSize?: number;\n  glassColor?: string;\n  staggerFrames?: number;\n  showTrafficLights?: boolean;\n  /** The soft cyan/purple gradient glow behind the glass. On by default. */\n  aura?: boolean;\n  /**\n   * Reveal the first line as a left-to-right typewriter (with a block cursor)\n   * instead of the default fade-up; the remaining lines then stagger in only\n   * after the first line finishes typing. Off by default.\n   */\n  typeFirstLine?: boolean;\n  /** Typing speed (characters/second) when `typeFirstLine` is on. */\n  firstLineCps?: number;\n  speed?: number;\n  className?: string;\n}\n\n/** Frames the first-line typewriter takes — exported so a caller can sync a\n *  camera move to the moment typing completes. */\nexport function firstLineTypeFrames(\n  firstLine: string,\n  firstLineCps: number,\n  fps: number,\n): number {\n  return Math.ceil((firstLine.length / firstLineCps) * fps);\n}\n\nconst FONT_MONO =\n  \"var(--font-geist-mono), ui-monospace, SFMono-Regular, monospace\";\n\nconst DEFAULT_CODE = `import { motion } from \"remotion\";\n\n// Generate a hero scene\nexport function Hero() {\n  const frame = useCurrentFrame();\n  const opacity = frame / 30;\n  return <h1 style={{ opacity }}>Hello</h1>;\n}`;\n\n// Minimal regex tokenizer. NOT a real syntax highlighter — just enough to\n// give the eye color anchors. Order matters: comments → strings → keywords.\nconst KEYWORDS = new Set([\n  \"import\",\n  \"from\",\n  \"export\",\n  \"function\",\n  \"const\",\n  \"let\",\n  \"var\",\n  \"return\",\n  \"if\",\n  \"else\",\n  \"for\",\n  \"while\",\n  \"new\",\n  \"class\",\n  \"extends\",\n  \"default\",\n  \"true\",\n  \"false\",\n  \"null\",\n  \"undefined\",\n]);\n\ntype Token = {\n  text: string;\n  kind: \"code\" | \"comment\" | \"string\" | \"keyword\" | \"number\";\n};\n\nfunction tokenizeLine(line: string): Token[] {\n  // Whole-line comment.\n  const trimmed = line.trimStart();\n  if (trimmed.startsWith(\"//\")) {\n    return [{ text: line, kind: \"comment\" }];\n  }\n\n  const tokens: Token[] = [];\n  // Split keeping delimiters: words, strings, numbers, everything else.\n  const re = /(\"[^\"]*\"|'[^']*'|`[^`]*`|\\b\\d+\\b|\\b[A-Za-z_$][\\w$]*\\b|[^\\w\"']+)/g;\n  let match: RegExpExecArray | null;\n  while ((match = re.exec(line)) !== null) {\n    const t = match[0];\n    const first = t[0];\n    if (first === '\"' || first === \"'\" || first === \"`\") {\n      tokens.push({ text: t, kind: \"string\" });\n    } else if (/^\\d+$/.test(t)) {\n      tokens.push({ text: t, kind: \"number\" });\n    } else if (/^[A-Za-z_$][\\w$]*$/.test(t) && KEYWORDS.has(t)) {\n      tokens.push({ text: t, kind: \"keyword\" });\n    } else {\n      tokens.push({ text: t, kind: \"code\" });\n    }\n  }\n  return tokens;\n}\n\nconst TOKEN_COLORS: Record<Token[\"kind\"], string> = {\n  code: \"#e4e4e7\",\n  comment: \"#52525b\",\n  string: \"#86efac\",\n  keyword: \"#c4b5fd\",\n  number: \"#fcd34d\",\n};\n\nexport function GlassCodeBlock({\n  code = DEFAULT_CODE,\n  title = \"hero.tsx\",\n  width = 760,\n  height = 460,\n  fontSize = 16,\n  glassColor = \"rgba(10, 10, 10, 0.6)\",\n  staggerFrames = 4,\n  showTrafficLights = true,\n  aura = true,\n  typeFirstLine = false,\n  firstLineCps = 30,\n  speed = 1,\n  className,\n}: GlassCodeBlockProps) {\n  const { fps } = useVideoConfig();\n  const lines = code.split(\"\\n\");\n  // When the first line types, every later line waits until typing is done.\n  const typeDur = typeFirstLine\n    ? firstLineTypeFrames(lines[0] ?? \"\", firstLineCps, fps)\n    : 0;\n\n  return (\n    <div\n      className={className}\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n      }}\n    >\n      {/* Animated background hint behind the glass so the blur has something\n          to chew on. Pure CSS — no extra deps. */}\n      {aura && <BackdropAura />}\n\n      {/* 1px gradient ring acting as a microborder */}\n      <div\n        style={{\n          position: \"relative\",\n          padding: 1,\n          borderRadius: 16,\n          background:\n            \"linear-gradient(180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0) 100%)\",\n          width,\n          height,\n          boxShadow: \"0 50px 120px rgba(0,0,0,0.55)\",\n        }}\n      >\n        <div\n          style={{\n            width: \"100%\",\n            height: \"100%\",\n            borderRadius: 15,\n            background: glassColor,\n            backdropFilter: \"blur(16px)\",\n            WebkitBackdropFilter: \"blur(16px)\",\n            boxShadow: \"inset 0 1px 0 rgba(255,255,255,0.06)\",\n            display: \"flex\",\n            flexDirection: \"column\",\n            overflow: \"hidden\",\n            fontFamily: FONT_MONO,\n          }}\n        >\n          {/* Chrome */}\n          <div\n            style={{\n              height: 40,\n              display: \"flex\",\n              alignItems: \"center\",\n              gap: 8,\n              padding: \"0 16px\",\n              borderBottom: \"1px solid rgba(255,255,255,0.06)\",\n            }}\n          >\n            {showTrafficLights && (\n              <>\n                <Light color=\"#ff5f57\" />\n                <Light color=\"#febc2e\" />\n                <Light color=\"#28c840\" />\n              </>\n            )}\n            <div\n              style={{\n                flex: 1,\n                textAlign: \"center\",\n                color: \"#a1a1aa\",\n                fontSize: 12,\n                letterSpacing: \"0.02em\",\n              }}\n            >\n              {title}\n            </div>\n          </div>\n\n          {/* Code body */}\n          <div\n            style={{\n              flex: 1,\n              padding: \"20px 24px\",\n              display: \"flex\",\n              flexDirection: \"column\",\n              gap: 4,\n              fontSize,\n              lineHeight: 1.55,\n            }}\n          >\n            {lines.map((line, i) => {\n              if (typeFirstLine && i === 0) {\n                return (\n                  <Sequence key={i} from={0} layout=\"none\">\n                    <TypedCodeLine\n                      line={line}\n                      index={i}\n                      fontSize={fontSize}\n                      cps={firstLineCps}\n                      fps={fps}\n                      speed={speed}\n                    />\n                  </Sequence>\n                );\n              }\n              const baseFrom = typeFirstLine\n                ? typeDur + (i - 1) * staggerFrames\n                : i * staggerFrames;\n              return (\n                <Sequence\n                  key={i}\n                  from={Math.round(baseFrom / speed)}\n                  layout=\"none\"\n                >\n                  <CodeLine line={line} index={i} fontSize={fontSize} />\n                </Sequence>\n              );\n            })}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction Light({ color }: { color: string }) {\n  return (\n    <div\n      style={{\n        width: 12,\n        height: 12,\n        borderRadius: \"50%\",\n        background: color,\n        opacity: 0.6,\n      }}\n    />\n  );\n}\n\nfunction CodeLine({\n  line,\n  index,\n  fontSize,\n}: {\n  line: string;\n  index: number;\n  fontSize: number;\n}) {\n  const frame = useCurrentFrame();\n  const opacity = interpolate(frame, [0, 8], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n  });\n  const ty = interpolate(frame, [0, 8], [4, 0], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n  });\n\n  const tokens = tokenizeLine(line);\n  // Render an empty line as a half-height spacer so blank lines still take\n  // visual space without collapsing the gap.\n  if (tokens.length === 0) {\n    return <div style={{ height: fontSize * 0.8, opacity }} />;\n  }\n  return (\n    <div\n      style={{\n        opacity,\n        transform: `translateY(${ty}px)`,\n        whiteSpace: \"pre\",\n        display: \"flex\",\n        gap: 0,\n      }}\n    >\n      <span style={{ width: 28, color: \"#3f3f46\", userSelect: \"none\" }}>\n        {String(index + 1).padStart(2, \" \")}\n      </span>\n      <span>\n        {tokens.map((t, i) => (\n          <span key={i} style={{ color: TOKEN_COLORS[t.kind] }}>\n            {t.text}\n          </span>\n        ))}\n      </span>\n    </div>\n  );\n}\n\nfunction TypedCodeLine({\n  line,\n  index,\n  fontSize,\n  cps,\n  fps,\n  speed,\n}: {\n  line: string;\n  index: number;\n  fontSize: number;\n  cps: number;\n  fps: number;\n  speed: number;\n}) {\n  const frame = useCurrentFrame() * speed;\n  const len = line.length;\n  const revealed = Math.floor(\n    interpolate(frame, [0, (len / cps) * fps], [0, len], {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n    }),\n  );\n  const visible = line.substring(0, revealed);\n  const done = revealed >= len;\n  // 2 Hz blink at any framerate.\n  const cursorOn = Math.floor((frame / fps) * 2) % 2 === 0;\n  const tokens = tokenizeLine(visible);\n\n  return (\n    <div\n      style={{\n        whiteSpace: \"pre\",\n        display: \"flex\",\n        gap: 0,\n        alignItems: \"center\",\n      }}\n    >\n      <span style={{ width: 28, color: \"#3f3f46\", userSelect: \"none\" }}>\n        {String(index + 1).padStart(2, \" \")}\n      </span>\n      <span>\n        {tokens.map((t, i) => (\n          <span key={i} style={{ color: TOKEN_COLORS[t.kind] }}>\n            {t.text}\n          </span>\n        ))}\n        {!done && cursorOn && (\n          <span\n            style={{\n              display: \"inline-block\",\n              width: fontSize * 0.55,\n              height: fontSize,\n              background: \"#e4e4e7\",\n              marginLeft: 1,\n              transform: \"translateY(2px)\",\n            }}\n          />\n        )}\n      </span>\n    </div>\n  );\n}\n\nfunction BackdropAura() {\n  // Slow, low-amplitude blob behind the glass so the backdrop blur has\n  // perceptible content to refract.\n  const frame = useCurrentFrame();\n  const t = frame / 60;\n  const x = 50 + Math.sin(t) * 20;\n  const y = 50 + Math.cos(t * 0.7) * 15;\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        pointerEvents: \"none\",\n        background: `radial-gradient(circle at ${x}% ${y}%, rgba(56,189,248,0.22), transparent 50%), radial-gradient(circle at ${100 - x}% ${100 - y}%, rgba(168,85,247,0.18), transparent 55%)`,\n      }}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/glass-code-block.tsx"
    },
    {
      "path": "src/demos/fonttrio/index.tsx",
      "content": "import React, { type CSSProperties, type ReactNode } from \"react\";\nimport { AbsoluteFill, Easing, 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/Manrope\";\nimport { loadFont as loadPlayfair } from \"@remotion/google-fonts/PlayfairDisplay\";\nimport { loadFont as loadSourceSerif } from \"@remotion/google-fonts/SourceSerif4\";\nimport { loadFont as loadMono } from \"@remotion/google-fonts/JetBrainsMono\";\nimport { loadFont as loadAbril } from \"@remotion/google-fonts/AbrilFatface\";\nimport { loadFont as loadBebas } from \"@remotion/google-fonts/BebasNeue\";\nimport { loadFont as loadUrbanist } from \"@remotion/google-fonts/Urbanist\";\n\nimport { RemocnUIProvider } from \"@/lib/remocn-ui\";\nimport { Backdrop } from \"@/components/remocn/backdrop\";\nimport { GlassCodeBlock } from \"@/components/remocn/glass-code-block\";\nimport { SharedAxisZ } from \"@/components/remocn/shared-axis-z\";\nimport { RollingNumber } from \"@/components/remocn/rolling-number\";\nimport { TerminalCursorZoom } from \"@/components/remocn/terminal-cursor-zoom\";\nimport { useBlurInTransition } from \"@/components/remocn/use-blur-in-transition\";\nimport { BlurIn } from \"@/components/remocn/blur-in\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\n\n// ---------------------------------------------------------------------------\n// Fonts. The whole point of a type product is to show the real typefaces — so\n// every specimen here renders in its actual family, not a stand-in.\n// ---------------------------------------------------------------------------\nconst { fontFamily: SANS_FAMILY } = loadSans(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\", \"500\", \"600\", \"700\"],\n});\nconst { fontFamily: PLAYFAIR_FAMILY } = loadPlayfair(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"500\", \"700\", \"800\"],\n});\nconst { fontFamily: SOURCE_SERIF_FAMILY } = loadSourceSerif(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\", \"600\"],\n});\nconst { fontFamily: MONO_FAMILY } = loadMono(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\", \"700\"],\n});\nconst { fontFamily: ABRIL_FAMILY } = loadAbril(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\"],\n});\nconst { fontFamily: BEBAS_FAMILY } = loadBebas(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\"],\n});\nconst { fontFamily: URBANIST_FAMILY } = loadUrbanist(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"600\", \"800\"],\n});\n\nconst SANS = `${SANS_FAMILY}, -apple-system, BlinkMacSystemFont, sans-serif`;\nconst MONO = `${MONO_FAMILY}, ui-monospace, SFMono-Regular, monospace`;\nconst PLAYFAIR = `${PLAYFAIR_FAMILY}, Georgia, serif`;\nconst SOURCE_SERIF = `${SOURCE_SERIF_FAMILY}, Georgia, serif`;\nconst ABRIL = `${ABRIL_FAMILY}, Georgia, serif`;\nconst BEBAS = `${BEBAS_FAMILY}, Impact, sans-serif`;\nconst URBANIST = `${URBANIST_FAMILY}, sans-serif`;\n\n// Editorial palette — warm ink + a single restrained gold accent.\nconst GOLD = \"#e0a23c\";\nconst INK = \"#fafafa\";\nconst MUTED = \"rgba(250,250,250,0.62)\";\nconst FAINT = \"rgba(250,250,250,0.4)\";\n\n// ---------------------------------------------------------------------------\n// Scene timings (frames @ 30fps), one per beat. Transitions overlap.\n// ---------------------------------------------------------------------------\nconst S_INTRO = 140; //  three pain questions via shared-axis-z\nconst S_THREE = 48; //   \"Three fonts.\" — the hook\nconst S_TRIO = 140; //   the editorial pairing — three real specimens\nconst S_INSTALL = 82; //  terminal-cursor-zoom install\nconst S_CODE = 132; //   generated CSS variables (GlassCodeBlock)\nconst S_COUNT = 88; //    78 curated pairings (rolling-number)\nconst S_WALL = 112; //   specimen wall across moods\nconst S_OUTRO = 112; //   wordmark + tagline + install pill\n\nconst T_ZOOM = 18; //  intro → three (punch in)\nconst T_X = 14; //      generic crossfade\nconst T_BLUR = 16; //   trio → install (background clash)\nconst T_OUT = 20; //    wall → outro\n\nexport const FONTTRIO_DURATION =\n  S_INTRO +\n  S_THREE +\n  S_TRIO +\n  S_INSTALL +\n  S_CODE +\n  S_COUNT +\n  S_WALL +\n  S_OUTRO -\n  (T_ZOOM + T_X + T_BLUR + T_X + T_X + T_X + T_OUT);\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?: 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// Mark — inline marker swipe behind a phrase.\n// ---------------------------------------------------------------------------\nconst Mark: React.FC<{\n  children: string;\n  color?: string;\n  startFrame?: number;\n}> = ({ children, color = GOLD, startFrame = 8 }) => {\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, \"#0a0a0a\"],\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.02em -0.12em\",\n          background: color,\n          borderRadius: 6,\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// Small \"trio\" mark — three type-weight bars, the middle one gold.\nconst TrioMark: React.FC<{ size?: number }> = ({ size = 60 }) => (\n  <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\">\n    <rect x={3} y={4} width={3.4} height={16} rx={1.7} fill={INK} />\n    <rect x={10.3} y={4} width={3.4} height={16} rx={1.7} fill={GOLD} />\n    <rect x={17.6} y={4} width={3.4} height={16} rx={1.7} fill={INK} />\n  </svg>\n);\n\n// ===========================================================================\n// Scene 1 — Intro. Three setup-the-pain questions, each via shared-axis-z.\n// ===========================================================================\nconst Q1 = \"Hand-picking fonts for every project?\";\nconst Q2 = \"Wiring CSS variables by hand?\";\nconst Q3 = \"Rebuilding the type scale each time?\";\n\nconst IntroScene: React.FC = () => (\n  <AbsoluteFill style={{ padding: \"0 90px\" }}>\n    <Series>\n      <Series.Sequence durationInFrames={42} layout=\"none\">\n        <SharedAxisZ\n          fromText=\"\"\n          toText={Q1}\n          fontSize={42}\n          fontWeight={600}\n          color={\"#FFFFFF\"}\n        />\n      </Series.Sequence>\n      <Series.Sequence durationInFrames={46} layout=\"none\">\n        <SharedAxisZ\n          fromText={Q1}\n          toText={Q2}\n          fontSize={42}\n          fontWeight={600}\n          color={\"#FFFFFF\"}\n        />\n      </Series.Sequence>\n      <Series.Sequence durationInFrames={52} layout=\"none\">\n        <SharedAxisZ\n          fromText={Q2}\n          toText={Q3}\n          fontSize={42}\n          fontWeight={600}\n          color={\"#FFFFFF\"}\n        />\n      </Series.Sequence>\n    </Series>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 2 — The hook. \"Three fonts.\" settles hard and centered.\n// ===========================================================================\nconst ThreeScene: React.FC = () => (\n  <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n    <SharedAxisZ\n      fromText={\"\"}\n      toText={\"Three fonts.\"}\n      fontSize={130}\n      fontWeight={700}\n      color={\"#FFFFFF\"}\n    />\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 3 — The editorial pairing. One pairing, three roles — each rendered in\n// its real typeface so the viewer reads the actual fonts.\n// ===========================================================================\nconst TrioRow: React.FC<{\n  role: string;\n  name: string;\n  family: string;\n  cssVar: string;\n  nameSize: number;\n  delay: number;\n  divider?: boolean;\n}> = ({ role, name, family, cssVar, nameSize, delay, divider = true }) => (\n  <Reveal delay={delay} distance={22} blur={10} duration={20}>\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"flex-end\",\n        justifyContent: \"space-between\",\n        gap: 24,\n        padding: \"20px 0\",\n        borderTop: divider ? \"1px solid rgba(250,250,250,0.1)\" : \"none\",\n      }}\n    >\n      <div style={{ display: \"flex\", flexDirection: \"column\", gap: 6 }}>\n        <span\n          style={{\n            fontFamily: SANS,\n            fontSize: 14,\n            fontWeight: 600,\n            color: GOLD,\n          }}\n        >\n          {role}\n        </span>\n        <span\n          style={{\n            fontFamily: family,\n            fontSize: nameSize,\n            color: INK,\n            lineHeight: 1,\n          }}\n        >\n          {name}\n        </span>\n      </div>\n      <span style={{ fontFamily: MONO, fontSize: 15, color: FAINT }}>\n        {cssVar}\n      </span>\n    </div>\n  </Reveal>\n);\n\nconst TrioScene: React.FC = () => (\n  <AbsoluteFill\n    style={{\n      alignItems: \"center\",\n      justifyContent: \"center\",\n      flexDirection: \"column\",\n      gap: 26,\n    }}\n  >\n    <Reveal delay={2} distance={16} blur={10}>\n      <div\n        style={{ display: \"flex\", alignItems: \"center\", gap: 14 }}\n      >\n        <span\n          style={{\n            fontFamily: MONO,\n            fontSize: 14,\n            color: GOLD,\n            padding: \"5px 12px\",\n            borderRadius: 999,\n            border: `1px solid ${GOLD}55`,\n            background: `${GOLD}1a`,\n          }}\n        >\n          editorial\n        </span>\n        <h2\n          style={{\n            margin: 0,\n            fontFamily: SANS,\n            fontWeight: 600,\n            fontSize: 34,\n            color: INK,\n          }}\n        >\n          One pairing, three roles\n        </h2>\n      </div>\n    </Reveal>\n    <Reveal delay={8} distance={36} blur={14} duration={22}>\n      <div\n        style={{\n          width: 740,\n          padding: \"12px 40px 24px\",\n          borderRadius: 20,\n          background: \"rgba(10,10,10,0.55)\",\n          border: `1px solid ${GOLD}3a`,\n          boxShadow: \"0 20px 50px rgba(0,0,0,0.45)\",\n        }}\n      >\n        <TrioRow\n          role=\"Heading\"\n          name=\"Playfair Display\"\n          family={PLAYFAIR}\n          cssVar=\"--font-heading\"\n          nameSize={46}\n          delay={16}\n          divider={false}\n        />\n        <TrioRow\n          role=\"Body\"\n          name=\"Source Serif 4\"\n          family={SOURCE_SERIF}\n          cssVar=\"--font-body\"\n          nameSize={38}\n          delay={24}\n        />\n        <TrioRow\n          role=\"Mono\"\n          name=\"JetBrains Mono\"\n          family={MONO}\n          cssVar=\"--font-mono\"\n          nameSize={30}\n          delay={32}\n        />\n      </div>\n    </Reveal>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 4 — Install. terminal-cursor-zoom dollies across the typed command.\n// ===========================================================================\nconst InstallScene: React.FC = () => (\n  <AbsoluteFill>\n    <TerminalCursorZoom\n      command=\"npx shadcn add @fonttrio/editorial\"\n      title=\"~/my-app\"\n      fontSize={22}\n      zoom={2.4}\n      charsPerFrame={1}\n    />\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 5 — What lands in your project: the generated CSS variables.\n// ===========================================================================\nconst CSS_EXAMPLE = `/* app/globals.css — added by Fonttrio */\n:root {\n  --font-heading: var(--font-playfair-display);\n  --font-body: var(--font-source-serif-4);\n  --font-mono: var(--font-jetbrains-mono);\n}\n\nh1, h2, h3 {\n  font-family: var(--font-heading);\n  font-weight: 700;\n}\n\np {\n  font-family: var(--font-body);\n  line-height: 1.65;\n}`;\n\nconst CodeScene: React.FC = () => (\n  <AbsoluteFill\n    style={{\n      alignItems: \"center\",\n      justifyContent: \"center\",\n      flexDirection: \"column\",\n      gap: 26,\n    }}\n  >\n    <Reveal delay={2} distance={16} blur={10}>\n      <h2\n        style={{\n          margin: 0,\n          fontFamily: SANS,\n          fontWeight: 600,\n          fontSize: 38,\n          color: INK,\n          textAlign: \"center\",\n        }}\n      >\n        Variables,{\" \"}\n        <Mark color={GOLD} startFrame={12}>\n          wired for you\n        </Mark>\n      </h2>\n    </Reveal>\n    <Reveal delay={8} distance={40} blur={14} duration={22}>\n      <div style={{ position: \"relative\", width: 760, height: 440 }}>\n        <GlassCodeBlock\n          code={CSS_EXAMPLE}\n          title=\"globals.css\"\n          width={760}\n          height={440}\n          fontSize={15}\n          staggerFrames={2}\n        />\n      </div>\n    </Reveal>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 6 — The library at a glance. A rolling number lands on the count.\n// ===========================================================================\nconst CountScene: React.FC = () => (\n  <AbsoluteFill\n    style={{\n      alignItems: \"center\",\n      justifyContent: \"center\",\n      flexDirection: \"column\",\n      gap: 10,\n    }}\n  >\n    <div style={{ height: 170, display: \"flex\", alignItems: \"center\" }}>\n      <RollingNumber from={0} to={78} fontSize={150} color={INK} speed={1.3} />\n    </div>\n    <Reveal delay={26} distance={14} blur={8}>\n      <span\n        style={{\n          fontFamily: SANS,\n          fontSize: 30,\n          fontWeight: 600,\n          color: INK,\n        }}\n      >\n        curated pairings\n      </span>\n    </Reveal>\n    <Reveal delay={36} distance={12} blur={6}>\n      <span style={{ fontFamily: SANS, fontSize: 18, color: MUTED }}>\n        editorial · clean · bold · corporate · creative\n      </span>\n    </Reveal>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 7 — Specimen wall. One \"Ag\" per mood, each in a real display face.\n// ===========================================================================\ntype Specimen = { label: string; name: string; family: string };\n\nconst SPECIMENS: Specimen[] = [\n  { label: \"Editorial\", name: \"Playfair Display\", family: PLAYFAIR },\n  { label: \"Bold\", name: \"Abril Fatface\", family: ABRIL },\n  { label: \"Impact\", name: \"Bebas Neue\", family: BEBAS },\n  { label: \"Clean\", name: \"Urbanist\", family: URBANIST },\n];\n\nconst SpecimenCard: React.FC<{ spec: Specimen; delay: number }> = ({\n  spec,\n  delay,\n}) => (\n  <Reveal delay={delay} distance={34} blur={12} duration={20}>\n    <div\n      style={{\n        width: 232,\n        height: 230,\n        display: \"flex\",\n        flexDirection: \"column\",\n        justifyContent: \"space-between\",\n        padding: \"20px 22px\",\n        borderRadius: 18,\n        background: \"rgba(10,10,10,0.5)\",\n        border: \"1px solid rgba(250,250,250,0.12)\",\n        boxShadow: \"0 16px 40px rgba(0,0,0,0.4)\",\n      }}\n    >\n      <span\n        style={{\n          alignSelf: \"flex-start\",\n          fontFamily: MONO,\n          fontSize: 13,\n          color: GOLD,\n          padding: \"4px 10px\",\n          borderRadius: 999,\n          background: `${GOLD}1a`,\n        }}\n      >\n        {spec.label}\n      </span>\n      <span\n        style={{\n          fontFamily: spec.family,\n          fontSize: 96,\n          color: INK,\n          lineHeight: 1,\n          textAlign: \"center\",\n        }}\n      >\n        Ag\n      </span>\n      <span style={{ fontFamily: SANS, fontSize: 16, color: MUTED }}>\n        {spec.name}\n      </span>\n    </div>\n  </Reveal>\n);\n\nconst WallScene: React.FC = () => (\n  <AbsoluteFill\n    style={{\n      alignItems: \"center\",\n      justifyContent: \"center\",\n      flexDirection: \"column\",\n      gap: 40,\n    }}\n  >\n    <Reveal delay={2} distance={16} blur={10}>\n      <h2\n        style={{\n          margin: 0,\n          fontFamily: SANS,\n          fontWeight: 600,\n          fontSize: 40,\n          color: INK,\n          textAlign: \"center\",\n        }}\n      >\n        Every mood, every project\n      </h2>\n    </Reveal>\n    <div style={{ display: \"flex\", gap: 24 }}>\n      {SPECIMENS.map((spec, i) => (\n        <SpecimenCard key={spec.name} spec={spec} delay={8 + i * 6} />\n      ))}\n    </div>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 8 — Outro. The trio mark, the wordmark in its own hero serif, the\n// tagline, and a copy-ready install pill.\n// ===========================================================================\nconst InstallPill: React.FC<{\n  command: string;\n  delay: number;\n  copyAt: number;\n}> = ({ command, delay, copyAt }) => {\n  const frame = useCurrentFrame();\n  const enter = interpolate(frame, [delay, delay + 16], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.bezier(0.22, 1, 0.36, 1),\n  });\n  const copied = frame >= copyAt;\n  const pop = interpolate(frame - copyAt, [0, 8], [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      style={{\n        display: \"inline-flex\",\n        alignItems: \"center\",\n        gap: 13,\n        height: 52,\n        padding: \"0 22px\",\n        borderRadius: 999,\n        border: `1px solid rgba(255,255,255,${copied ? 0.22 : 0.14})`,\n        background: \"rgba(255,255,255,0.05)\",\n        backdropFilter: \"blur(10px)\",\n        WebkitBackdropFilter: \"blur(10px)\",\n        boxShadow: \"0 12px 34px rgba(0,0,0,0.3)\",\n        fontFamily: MONO,\n        fontSize: 18,\n        opacity: enter,\n        transform: `translateY(${(1 - enter) * 12}px)`,\n      }}\n    >\n      <span style={{ color: FAINT }}>$</span>\n      <span style={{ color: INK }}>{command}</span>\n      <span\n        style={{\n          display: \"inline-flex\",\n          marginLeft: 3,\n          color: copied ? \"#4ade80\" : \"rgba(250,250,250,0.55)\",\n          transform: copied ? `scale(${pop})` : \"scale(1)\",\n        }}\n      >\n        {copied ? <CheckIcon size={17} /> : <CopyIcon size={17} />}\n      </span>\n    </div>\n  );\n};\n\nconst OutroScene: React.FC = () => (\n  <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n    <div\n      style={{\n        display: \"flex\",\n        flexDirection: \"column\",\n        alignItems: \"center\",\n        gap: 14,\n      }}\n    >\n      <Reveal delay={2} distance={10} blur={6} duration={14}>\n        <TrioMark size={60} />\n      </Reveal>\n      <Reveal delay={10} distance={16} blur={14} duration={22}>\n        <div\n          style={{\n            fontFamily: PLAYFAIR,\n            fontWeight: 700,\n            fontSize: 96,\n            color: INK,\n            lineHeight: 1,\n          }}\n        >\n          Fonttrio\n        </div>\n      </Reveal>\n      <Reveal delay={22} distance={12} blur={8}>\n        <span style={{ fontFamily: SANS, fontSize: 24, color: MUTED }}>\n          Three fonts. One command.\n        </span>\n      </Reveal>\n      <Reveal delay={34} distance={12} blur={8}>\n        <div style={{ marginTop: 10 }}>\n          <InstallPill\n            command=\"npx shadcn add @fonttrio/editorial\"\n            delay={34}\n            copyAt={60}\n          />\n        </div>\n      </Reveal>\n    </div>\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\nconst BlurCrossfade: React.FC<\n  TransitionPresentationComponentProps<EmptyProps>\n> = ({ children, presentationProgress, presentationDirection }) => {\n  const entering = presentationDirection === \"entering\";\n  const p = presentationProgress;\n  const style: React.CSSProperties = entering\n    ? {\n        opacity: p,\n        transform: `scale(${0.96 + p * 0.04})`,\n        filter: p < 1 ? `blur(${(1 - p) * 14}px)` : undefined,\n      }\n    : {\n        opacity: 1 - p,\n        transform: `scale(${1 + p * 0.04})`,\n        filter: p > 0 ? `blur(${p * 14}px)` : undefined,\n      };\n  return <AbsoluteFill style={style}>{children}</AbsoluteFill>;\n};\nconst blurCrossfade = (): TransitionPresentation<EmptyProps> => ({\n  component: BlurCrossfade,\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.84 + p * 0.16})`,\n        filter: p < 1 ? `blur(${(1 - p) * 20}px)` : undefined,\n      }\n    : {\n        opacity: 1 - p,\n        transform: `translateY(${-p * rise}px) scale(${1 + p * 0.22})`,\n        filter: p > 0 ? `blur(${p * 20}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 FonttrioDemo: React.FC = () => {\n  return (\n    <RemocnUIProvider>\n      <AbsoluteFill style={{ fontFamily: SANS }}>\n        {/* Persistent image background for the whole video. */}\n        <Backdrop fill={{ type: \"image\", src: demoAsset(\"bg.png\") }} />\n        {/* Scrim to deepen contrast for foreground content. */}\n        <AbsoluteFill\n          style={{\n            background:\n              \"radial-gradient(120% 120% at 50% 40%, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.5) 100%)\",\n          }}\n        />\n\n        <TransitionSeries>\n          {/* 1 — Intro questions */}\n          <TransitionSeries.Sequence durationInFrames={S_INTRO}>\n            <IntroScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_ZOOM })}\n            presentation={zoomBlur(40)}\n          />\n\n          {/* 2 — Three fonts. */}\n          <TransitionSeries.Sequence durationInFrames={S_THREE}>\n            <ThreeScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_X })}\n            presentation={crossfade()}\n          />\n\n          {/* 3 — The editorial pairing */}\n          <TransitionSeries.Sequence durationInFrames={S_TRIO}>\n            <TrioScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_BLUR })}\n            presentation={blurCrossfade()}\n          />\n\n          {/* 4 — Install (terminal-cursor-zoom) */}\n          <TransitionSeries.Sequence durationInFrames={S_INSTALL}>\n            <InstallScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_X })}\n            presentation={crossfade()}\n          />\n\n          {/* 5 — Generated CSS variables */}\n          <TransitionSeries.Sequence durationInFrames={S_CODE}>\n            <CodeScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_X })}\n            presentation={crossfade()}\n          />\n\n          {/* 6 — Count */}\n          <TransitionSeries.Sequence durationInFrames={S_COUNT}>\n            <CountScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_X })}\n            presentation={crossfade()}\n          />\n\n          {/* 7 — Specimen wall */}\n          <TransitionSeries.Sequence durationInFrames={S_WALL}>\n            <WallScene />\n          </TransitionSeries.Sequence>\n          <TransitionSeries.Transition\n            timing={linearTiming({ durationInFrames: T_OUT })}\n            presentation={zoomBlur(70)}\n          />\n\n          {/* 8 — Outro */}\n          <TransitionSeries.Sequence durationInFrames={S_OUTRO}>\n            <OutroScene />\n          </TransitionSeries.Sequence>\n        </TransitionSeries>\n      </AbsoluteFill>\n    </RemocnUIProvider>\n  );\n};\n",
      "type": "registry:component",
      "target": "demos/fonttrio/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/fonttrio/prompt.md",
      "content": "<!-- TODO(draft): placeholder written by AI — replace with the real prompt used to generate this video -->\n\nMake a type-forward spot for Fonttrio, our curated font-pairing registry for shadcn/ui. Open with a few pain questions about picking fonts, land a punchy \"Three fonts.\" hook, then reveal the editorial pairing — headline, body, and mono roles — each one actually set in its real typeface (Playfair Display, Source Serif 4, JetBrains Mono) so people can see the pairing, not just hear about it. Show the install command zooming in (npx shadcn add @fonttrio/editorial), pop open a glass code block with the generated CSS variables, roll up a count of the curated pairings (78), and do a specimen wall of \"Ag\" across a few different moods like editorial, bold, impact, clean. Close with the trio mark, the Fonttrio wordmark set in Playfair Display, the tagline, and a copy-ready install pill. Use remocn for the typewriter and glass code block pieces.\n",
      "type": "registry:file",
      "target": "demos/fonttrio/prompt.md"
    }
  ],
  "docs": "Register the composition in your Remotion Root:\n\n  import { FonttrioDemo } from \"@/demos/fonttrio\";\n  <Composition id=\"fonttrio\" component={FonttrioDemo} durationInFrames={744} fps={30} width={1280} height={720} />\n\nRequires Tailwind v4 wired into Remotion (@remotion/tailwind-v4).\nRender locally: npx remotion render fonttrio out/fonttrio.mp4",
  "type": "registry:block"
}