{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sponsor-canadian-ai",
  "title": "remocn ✕ Canadian AI — New sponsor",
  "description": "remocn demo composition \"remocn ✕ Canadian AI — New sponsor\" — installs the full Remotion composition. Generated with AI from the prompt in demos/sponsor-canadian-ai/prompt.md.",
  "dependencies": [
    "@paper-design/shaders-react",
    "@remotion/google-fonts",
    "@remotion/transitions",
    "remotion"
  ],
  "registryDependencies": [
    "https://remocn.dev/r/focus-pull.json",
    "https://remocn.dev/r/push-through.json",
    "https://remocn.dev/r/shader-grain-gradient.json",
    "https://remocn.dev/r/soft-blur-in.json"
  ],
  "files": [
    {
      "path": "src/demos/sponsor-canadian-ai/index.tsx",
      "content": "import React from \"react\";\nimport { AbsoluteFill, Easing, interpolate, spring, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport { TransitionSeries, linearTiming, type TransitionPresentation, type TransitionPresentationComponentProps } from \"@remotion/transitions\";\nimport { loadFont as loadInter } from \"@remotion/google-fonts/Inter\";\nimport { loadFont as loadPlayfair } from \"@remotion/google-fonts/PlayfairDisplay\";\n\nimport { ShaderGrainGradient } from \"@/components/remocn/shader-grain-gradient\";\nimport { SoftBlurIn } from \"@/components/remocn/soft-blur-in\";\nimport { pushThrough } from \"@/components/remocn/push-through\";\nimport { focusPull } from \"@/components/remocn/focus-pull\";\n\n// Canadian AI speaks in an editorial register — Inter for the interface voice,\n// Playfair Display for the serif statements, exactly as canadian-ai.ca pairs them.\nconst { fontFamily: INTER_FAMILY } = loadInter(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\", \"500\", \"600\", \"700\"],\n});\nconst { fontFamily: SERIF_FAMILY } = loadPlayfair(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"500\"],\n});\nloadPlayfair(\"italic\", { subsets: [\"latin\"], weights: [\"500\"] });\n\nconst SANS = `${INTER_FAMILY}, -apple-system, BlinkMacSystemFont, sans-serif`;\nconst SERIF = `${SERIF_FAMILY}, Georgia, serif`;\n\n// Canadian AI palette — the site is paper-white with black ink and one amber\n// accent (#F59E0B, their \"live\" dots and Start buttons). The only light-theme\n// sponsor spot in the collection, because the brand is light.\nconst PAPER = \"#FCFBF9\";\nconst INK = \"#111110\";\nconst MUTED = \"rgba(17,17,16,0.6)\";\nconst FAINT = \"rgba(17,17,16,0.38)\";\nconst AMBER = \"#F59E0B\";\n\nconst clampOpts = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\n\n// ---------------------------------------------------------------------------\n// Scene timings (frames @ 30fps). Transitions overlap.\n// ---------------------------------------------------------------------------\nconst S_INTRO = 70; //   the mark draws itself on, amber live-dot pops\nconst S_HOOK = 66; //    \"Say hello to my new sponsor\"\nconst S_REVEAL = 110; // Canadian AI wordmark + serif tagline\nconst S_PHILOSOPHY = 76; // \"Better tools create more value.\"\nconst S_LOCKUP = 120; // Remocn ✕ Canadian AI + canadian-ai.ca\n\n// \"What we build\" — the site's own numbered product list, cut into beats.\nconst PRODUCTS = [\n  {\n    num: \"01\",\n    name: \"Core\",\n    blurb: \"Unified identity, workflows, and system foundation\",\n  },\n  {\n    num: \"02\",\n    name: \"Sales Agent\",\n    blurb: \"Automation for outreach, follow-up, and growth\",\n  },\n  {\n    num: \"03\",\n    name: \"Dev Agent\",\n    blurb: \"Operates across repos, builds, and technical workflows\",\n  },\n];\nconst PRODUCT_DURS = [44, 44, 50];\nconst S_BUILD = PRODUCT_DURS.reduce((a, b) => a + b, 0);\n\nconst T_PT = 16; // push-through\nconst T_X = 14; //  crossfade\nconst T_FP = 16; // focus-pull\n\nexport const SPONSOR_CANADIAN_AI_DURATION =\n  S_INTRO +\n  S_HOOK +\n  S_REVEAL +\n  S_BUILD +\n  S_PHILOSOPHY +\n  S_LOCKUP -\n  (T_PT + T_X + T_FP + T_X + T_PT);\n\n// The real Canadian AI mark from canadian-ai.ca/icon.svg — a single\n// monochrome path in a 1080 box. With drawProgress it draws itself on as a\n// stroke while the fill resolves via fillProgress.\nconst LOGO_PATH =\n  \"M1080,477.72v104.55c-19.84,255.58-221.7,461.02-474.67,491.57-21.43,2.58-43.24,6.16-65.33,6.16-54.13,0-106.38-10.24-155.64-25.12-4.06-11.11-3.07-24.13,4.96-35.2l202.02-279.04s0-.02,0-.03c6.37-8.82,9.79-19.38,9.79-30.25v-56.18c0-44.75-53.1-68.29-86.26-38.22l-113.72,103.09s0,.01-.03.03c-20.39,18.44-51.67,17.67-71.11-1.78l-88.8-88.8c-19.63-19.64-20.21-51.29-1.29-71.63l252.97-271.94.02-.02c30.69-33,7.27-86.73-37.8-86.73h-29.65c-13.7,0-26.81,5.44-36.49,15.11h-.02S88.09,514.19,88.09,514.19c-.03.03-.08.08-.11.11L0,602.28v-104.55C19.37,248.14,212.34,46.37,456.96,8.6c5.88-.9,11.77-1.71,17.71-2.44,21.43-2.58,43.24-6.16,65.33-6.16,54.13,0,106.38,10.24,155.64,25.12,4.06,11.11,3.07,24.13-4.96,35.2l-202.02,279.04s0,.02,0,.03c-6.37,8.82-9.79,19.4-9.79,30.25v56.18c0,44.75,53.1,68.29,86.26,38.22l113.72-103.09s0-.01.03-.03c20.39-18.44,51.67-17.67,71.11,1.78l88.8,88.8c19.63,19.64,20.21,51.29,1.29,71.63l-252.97,271.94-.02.02c-30.69,33-7.27,86.73,37.8,86.73h29.65c13.7,0,26.81-5.44,36.49-15.11h.02s300.87-300.9,300.87-300.9c.03-.03.08-.08.11-.11l87.98-87.98Z\";\n\nconst CanadianAiMark: React.FC<{\n  size?: number;\n  color?: string;\n  drawProgress?: number;\n  fillProgress?: number;\n}> = ({ size = 96, color = INK, drawProgress, fillProgress }) => {\n  const drawing = drawProgress !== undefined;\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 1080 1080\"\n      width={size}\n      height={size}\n      aria-hidden\n    >\n      <path\n        d={LOGO_PATH}\n        fill={color}\n        {...(drawing\n          ? {\n              pathLength: 1,\n              strokeDasharray: 1,\n              strokeDashoffset: 1 - drawProgress,\n              stroke: color,\n              strokeWidth: 18,\n              strokeLinecap: \"round\" as const,\n              fillOpacity: fillProgress ?? 1,\n            }\n          : {})}\n      />\n    </svg>\n  );\n};\n\n// ---------------------------------------------------------------------------\n// Slow camera drift — every scene rides a barely-there push-in so no frame\n// is ever static. durationInFrames is Sequence-scoped inside TransitionSeries.\n// ---------------------------------------------------------------------------\nconst Drift: React.FC<{ children: React.ReactNode; grow?: number }> = ({\n  children,\n  grow = 0.035,\n}) => {\n  const frame = useCurrentFrame();\n  const { durationInFrames } = useVideoConfig();\n  const scale = interpolate(frame, [0, durationInFrames], [1, 1 + grow]);\n  return (\n    <AbsoluteFill style={{ transform: `scale(${scale})` }}>\n      {children}\n    </AbsoluteFill>\n  );\n};\n\n// ---------------------------------------------------------------------------\n// Per-word rise — each word resolves out of blur while rising onto the\n// baseline. Takes the full type voice so the serif beats can use it too.\n// ---------------------------------------------------------------------------\nconst WordsRise: React.FC<{\n  text: string;\n  fontSize: number;\n  color?: string;\n  delay?: number;\n  stagger?: number;\n  fontFamily?: string;\n  fontStyle?: \"normal\" | \"italic\";\n  fontWeight?: number;\n}> = ({\n  text,\n  fontSize,\n  color = INK,\n  delay = 0,\n  stagger = 4,\n  fontFamily = SANS,\n  fontStyle = \"normal\",\n  fontWeight = 500,\n}) => {\n  const frame = useCurrentFrame();\n  const ease = Easing.bezier(0.2, 0.8, 0.2, 1);\n  const words = text.split(\" \");\n  return (\n    <span\n      style={{\n        fontFamily,\n        fontStyle,\n        fontWeight,\n        fontSize,\n        color,\n        lineHeight: 1.3,\n      }}\n    >\n      {words.map((word, i) => {\n        const local = frame - delay - i * stagger;\n        const p = interpolate(local, [0, 24], [0, 1], {\n          ...clampOpts,\n          easing: ease,\n        });\n        return (\n          <span\n            key={i}\n            style={{\n              display: \"inline-block\",\n              whiteSpace: \"pre\",\n              opacity: p,\n              transform: `translateY(${(1 - p) * 26}px)`,\n              filter: p < 1 ? `blur(${(1 - p) * 9}px)` : undefined,\n            }}\n          >\n            {word}\n            {i < words.length - 1 ? \" \" : \"\"}\n          </span>\n        );\n      })}\n    </span>\n  );\n};\n\n// ===========================================================================\n// Scene 1 — Intro. The Canadian AI mark draws itself on in black ink over the\n// paper and the fill resolves. A gentle float starts once the mark is drawn.\n// ===========================================================================\nconst MARK_INTRO_SIZE = 190;\n\nconst IntroScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const draw = interpolate(frame, [4, 42], [0, 1], {\n    ...clampOpts,\n    easing: Easing.inOut(Easing.cubic),\n  });\n  const fill = interpolate(frame, [34, 52], [0, 1], clampOpts);\n  // Float only after the ink has settled — a steady hand draws, then breathes.\n  const floatIn = interpolate(frame, [46, 64], [0, 1], clampOpts);\n  const float = Math.sin(frame / 14) * 6 * floatIn;\n  return (\n    <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n      <div\n        style={{\n          transform: `translateY(${float}px)`,\n          filter: \"drop-shadow(0 16px 28px rgba(31,26,16,0.16))\",\n        }}\n      >\n        <CanadianAiMark\n          size={MARK_INTRO_SIZE}\n          drawProgress={draw}\n          fillProgress={fill}\n        />\n      </div>\n    </AbsoluteFill>\n  );\n};\n\n// ===========================================================================\n// Scene 2 — Hook. The family line, in ink on paper.\n// ===========================================================================\nconst HookScene: React.FC = () => (\n  <Drift>\n    <AbsoluteFill style={{ padding: \"0 90px\" }}>\n      <SoftBlurIn\n        text=\"Say hello to my new sponsor\"\n        fontSize={54}\n        fontWeight={600}\n        color={INK}\n        blur={14}\n      />\n    </AbsoluteFill>\n  </Drift>\n);\n\n// ===========================================================================\n// Scene 3 — Reveal. The Canadian AI wordmark resolves out of depth, then the\n// site's serif tagline settles beneath it in Playfair italic.\n// ===========================================================================\nconst RevealScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const p = interpolate(frame, [4, 34], [0, 1], {\n    ...clampOpts,\n    easing: Easing.out(Easing.cubic),\n  });\n  const scale = interpolate(p, [0, 1], [1.12, 1]);\n  return (\n    <Drift>\n      <AbsoluteFill\n        style={{ alignItems: \"center\", justifyContent: \"center\", gap: 8 }}\n      >\n        <span\n          style={{\n            fontFamily: SANS,\n            fontWeight: 600,\n            fontSize: 92,\n            letterSpacing: \"-0.03em\",\n            color: INK,\n            opacity: p,\n            transform: `scale(${scale})`,\n            filter: p < 1 ? `blur(${(1 - p) * 20}px)` : undefined,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          Canadian AI\n        </span>\n        <WordsRise\n          text=\"Operating System for Organizations\"\n          fontSize={31}\n          color={MUTED}\n          delay={28}\n          stagger={3}\n          fontFamily={SERIF}\n          fontStyle=\"italic\"\n        />\n      </AbsoluteFill>\n    </Drift>\n  );\n};\n\n// ===========================================================================\n// Scene 4 — What we build. The site's numbered product list cut into\n// hard-cut beats: an amber serif numeral, the product name, one descriptor.\n// The kicker holds through the cuts.\n// ===========================================================================\nconst BEAT_STARTS = PRODUCT_DURS.map((_, i) =>\n  PRODUCT_DURS.slice(0, i).reduce((a, b) => a + b, 0),\n);\n\nconst BuildScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  let active = 0;\n  for (let i = 0; i < BEAT_STARTS.length; i++) {\n    if (frame >= BEAT_STARTS[i]) active = i;\n  }\n  const local = frame - BEAT_STARTS[active];\n  const p = interpolate(local, [0, 9], [0, 1], {\n    ...clampOpts,\n    easing: Easing.out(Easing.cubic),\n  });\n  const scale = interpolate(p, [0, 1], [1.1, 1]);\n  const kicker = interpolate(frame, [0, 14], [0, 1], clampOpts);\n  const beat = PRODUCTS[active];\n  return (\n    <Drift>\n      <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n        {/* Kicker — persists across the hard cuts. */}\n        <span\n          style={{\n            position: \"absolute\",\n            top: 128,\n            fontFamily: SANS,\n            fontWeight: 500,\n            fontSize: 19,\n            color: FAINT,\n            opacity: kicker,\n          }}\n        >\n          What we build\n        </span>\n        <div\n          style={{\n            display: \"flex\",\n            flexDirection: \"column\",\n            alignItems: \"center\",\n            gap: 10,\n            opacity: p,\n            transform: `scale(${scale})`,\n            filter: p < 1 ? `blur(${(1 - p) * 8}px)` : undefined,\n          }}\n        >\n          <span\n            style={{\n              fontFamily: SERIF,\n              fontWeight: 500,\n              fontSize: 64,\n              lineHeight: 1,\n              color: AMBER,\n            }}\n          >\n            {beat.num}\n          </span>\n          <span\n            style={{\n              fontFamily: SANS,\n              fontWeight: 600,\n              fontSize: 76,\n              letterSpacing: \"-0.02em\",\n              lineHeight: 1.1,\n              color: INK,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {beat.name}\n          </span>\n          <span\n            style={{\n              fontFamily: SANS,\n              fontWeight: 400,\n              fontSize: 23,\n              color: MUTED,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {beat.blurb}\n          </span>\n        </div>\n      </AbsoluteFill>\n    </Drift>\n  );\n};\n\n// ===========================================================================\n// Scene 5 — Philosophy. The site's lead creed, set as a serif statement.\n// ===========================================================================\nconst PhilosophyScene: React.FC = () => (\n  <Drift>\n    <AbsoluteFill\n      style={{\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        padding: \"0 120px\",\n        textAlign: \"center\",\n      }}\n    >\n      <WordsRise\n        text=\"Better tools create more value.\"\n        fontSize={56}\n        delay={4}\n        stagger={5}\n        fontFamily={SERIF}\n        fontStyle=\"italic\"\n      />\n    </AbsoluteFill>\n  </Drift>\n);\n\n// ===========================================================================\n// Scene 6 — Lockup. Remocn ✕ Canadian AI glides together from both sides.\n// ===========================================================================\nconst LockupScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const { fps } = useVideoConfig();\n  const ease = Easing.out(Easing.cubic);\n\n  const sideP = interpolate(frame, [6, 30], [0, 1], {\n    ...clampOpts,\n    easing: ease,\n  });\n  const leftX = (1 - sideP) * -70;\n  const rightX = (1 - sideP) * 70;\n  const sideBlur = (1 - sideP) * 12;\n\n  const cross = spring({\n    frame: frame - 26,\n    fps,\n    config: { damping: 12, stiffness: 160, mass: 0.8 },\n  });\n  const crossOpacity = interpolate(frame, [26, 38], [0, 1], clampOpts);\n\n  return (\n    <Drift grow={0.05}>\n      <AbsoluteFill\n        style={{ alignItems: \"center\", justifyContent: \"center\" }}\n      >\n        <div style={{ display: \"flex\", alignItems: \"center\", gap: 36 }}>\n          <span\n            style={{\n              fontFamily: SANS,\n              fontWeight: 600,\n              fontSize: 64,\n              letterSpacing: \"-0.02em\",\n              color: INK,\n              opacity: sideP,\n              transform: `translateX(${leftX}px)`,\n              filter: sideBlur > 0.2 ? `blur(${sideBlur}px)` : undefined,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            Remocn\n          </span>\n          <span\n            style={{\n              fontFamily: SANS,\n              fontWeight: 400,\n              fontSize: 42,\n              color: FAINT,\n              opacity: crossOpacity,\n              transform: `scale(${interpolate(cross, [0, 1], [0.4, 1])})`,\n            }}\n          >\n            ✕\n          </span>\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"center\",\n              gap: 16,\n              opacity: sideP,\n              transform: `translateX(${rightX}px)`,\n              filter: sideBlur > 0.2 ? `blur(${sideBlur}px)` : undefined,\n            }}\n          >\n            <CanadianAiMark size={56} />\n            <span\n              style={{\n                fontFamily: SANS,\n                fontWeight: 600,\n                fontSize: 56,\n                letterSpacing: \"-0.02em\",\n                color: INK,\n                whiteSpace: \"nowrap\",\n              }}\n            >\n              Canadian AI\n            </span>\n          </div>\n        </div>\n      </AbsoluteFill>\n    </Drift>\n  );\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// ===========================================================================\n// Composition root. Paper-white canvas — the only light sponsor spot in the\n// collection, because Canadian AI is a light brand. A warm grain gradient\n// textures the paper, washed back toward the center so content sits on\n// near-clean white.\n// ===========================================================================\nexport const SponsorCanadianAiDemo: React.FC = () => {\n  return (\n    <AbsoluteFill\n      style={\n        {\n          \"--font-geist-sans\": INTER_FAMILY,\n          background: PAPER,\n        } as React.CSSProperties\n      }\n    >\n      {/* Persistent warm-paper grain backdrop. */}\n      <ShaderGrainGradient\n        speed={0.5}\n        colorBack={PAPER}\n        colors={[\"#F1EAD9\", \"#EDE3CE\", \"#F7F2E6\"]}\n        softness={0.85}\n        intensity={0.16}\n        noise={0.06}\n      />\n      {/* Paper wash — keeps the center clean, lets the edges keep the grain. */}\n      <AbsoluteFill\n        style={{\n          background:\n            \"radial-gradient(120% 120% at 50% 42%, rgba(252,251,249,0.8) 0%, rgba(252,251,249,0.28) 100%)\",\n        }}\n      />\n\n      <TransitionSeries>\n        {/* 1 — The mark draws itself on */}\n        <TransitionSeries.Sequence durationInFrames={S_INTRO}>\n          <IntroScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_PT })}\n          presentation={pushThrough()}\n        />\n\n        {/* 2 — Hook */}\n        <TransitionSeries.Sequence durationInFrames={S_HOOK}>\n          <HookScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_X })}\n          presentation={crossfade()}\n        />\n\n        {/* 3 — Wordmark + tagline */}\n        <TransitionSeries.Sequence durationInFrames={S_REVEAL}>\n          <RevealScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_FP })}\n          presentation={focusPull()}\n        />\n\n        {/* 4 — What we build: 01 / 02 / 03 */}\n        <TransitionSeries.Sequence durationInFrames={S_BUILD}>\n          <BuildScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_X })}\n          presentation={crossfade()}\n        />\n\n        {/* 5 — Philosophy */}\n        <TransitionSeries.Sequence durationInFrames={S_PHILOSOPHY}>\n          <PhilosophyScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_PT })}\n          presentation={pushThrough()}\n        />\n\n        {/* 6 — Lockup + URL */}\n        <TransitionSeries.Sequence durationInFrames={S_LOCKUP}>\n          <LockupScene />\n        </TransitionSeries.Sequence>\n      </TransitionSeries>\n    </AbsoluteFill>\n  );\n};\n",
      "type": "registry:component",
      "target": "demos/sponsor-canadian-ai/index.tsx"
    },
    {
      "path": "src/demos/sponsor-canadian-ai/prompt.md",
      "content": "Make a sponsor shoutout video for Canadian AI (canadian-ai.ca) — pull their style, logo, and colors from the site. Their brand is light and editorial: paper-white background, black ink, one amber accent (#F59E0B), Inter paired with Playfair Display serif italics, and a monochrome swirl mark. Keep the family grammar of the other sponsor spots but make this the light one. Open on their mark drawing itself on in ink, then \"Say hello to my new sponsor\" in a soft blur-in. Reveal the Canadian AI wordmark with the \"Operating System for Organizations\" tagline settling tight under it in serif italic. Cut their \"What we build\" list into three numbered beats — 01 Core / 02 Sales Agent / 03 Dev Agent — with amber serif numerals, then a serif philosophy beat: \"Better tools create more value.\" Close on the Remocn ✕ Canadian AI lockup alone, centered. Warm paper-grain shader behind everything, slow camera drift on every scene, push-through and focus-pull between scenes.\n",
      "type": "registry:file",
      "target": "demos/sponsor-canadian-ai/prompt.md"
    }
  ],
  "docs": "Register the composition in your Remotion Root:\n\n  import { SponsorCanadianAiDemo } from \"@/demos/sponsor-canadian-ai\";\n  <Composition id=\"sponsor-canadian-ai\" component={SponsorCanadianAiDemo} durationInFrames={504} fps={30} width={1280} height={720} />\n\nRequires Tailwind v4 wired into Remotion (@remotion/tailwind-v4).\nRender locally: npx remotion render sponsor-canadian-ai out/sponsor-canadian-ai.mp4 --gl=angle",
  "type": "registry:block"
}