reelkit-cli 0.8.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +13 -1
  2. package/package.json +49 -10
  3. package/skill/.claude-plugin/plugin.json +7 -0
  4. package/skill/SKILL.md +44 -19
  5. package/skill/THIRD_PARTY.md +37 -5
  6. package/skill/commands/launch-film.md +1 -1
  7. package/skill/hooks/hooks.json +1 -0
  8. package/skill/hooks/player.ts +278 -0
  9. package/skill/hooks/project.ts +227 -0
  10. package/skill/hooks/register.tsx +533 -0
  11. package/skill/hooks/timeline.ts +218 -0
  12. package/skill/reference/art-styles.md +70 -0
  13. package/skill/reference/backgrounds.md +1 -1
  14. package/skill/reference/brand-motion.md +62 -0
  15. package/skill/reference/clips.md +3 -3
  16. package/skill/reference/component-authoring.md +3 -1
  17. package/skill/reference/continuity.md +5 -5
  18. package/skill/reference/delivery-review.md +40 -0
  19. package/skill/reference/hebrew-rtl.md +3 -4
  20. package/skill/reference/higgsfield-production.md +70 -0
  21. package/skill/reference/{remotion-composition.md → hyperframes-composition.md} +14 -8
  22. package/skill/reference/kit.md +7 -3
  23. package/skill/reference/launch-film.md +13 -9
  24. package/skill/reference/motion-design.md +26 -22
  25. package/skill/reference/sound-design.md +34 -12
  26. package/skill/reference/studio-editing.md +55 -0
  27. package/skill/reference/studio-pane.md +31 -0
  28. package/skill/reference/style-components.md +40 -0
  29. package/skill/reference/style-recipes.md +86 -0
  30. package/skill/reference/styles.md +17 -10
  31. package/skill/reference/three-d.md +8 -7
  32. package/skill/reference/voice-sync.md +1 -1
  33. package/skill/tests/studio.test.ts +111 -0
  34. package/skill/types/index.d.ts +65 -0
  35. package/src/commands/assets.ts +5 -1
  36. package/src/contract/index.ts +1 -1
  37. package/src/hyperframes/Root.tsx +1 -0
  38. package/src/hyperframes/fonts.ts +54 -0
  39. package/src/hyperframes/frame.tsx +46 -0
  40. package/src/hyperframes/host.tsx +38 -0
  41. package/src/{remotion → hyperframes}/kit/Assemble3D.tsx +1 -1
  42. package/src/hyperframes/kit/BrandTransform3D.tsx +12 -0
  43. package/src/{remotion → hyperframes}/kit/BrowserFrame.tsx +1 -1
  44. package/src/{remotion → hyperframes}/kit/Camera.tsx +1 -1
  45. package/src/{remotion → hyperframes}/kit/Captions.tsx +1 -1
  46. package/src/{remotion → hyperframes}/kit/Card3D.tsx +2 -2
  47. package/src/{remotion → hyperframes}/kit/Carry.tsx +1 -1
  48. package/src/{remotion → hyperframes}/kit/ChapterFrame.tsx +1 -1
  49. package/src/{remotion → hyperframes}/kit/ClipLayer.tsx +1 -1
  50. package/src/{remotion → hyperframes}/kit/Counter.tsx +1 -1
  51. package/src/{remotion → hyperframes}/kit/CounterRoll.tsx +1 -1
  52. package/src/{remotion → hyperframes}/kit/Entrance.tsx +1 -1
  53. package/src/{remotion → hyperframes}/kit/FootageLayer.tsx +1 -1
  54. package/src/{remotion → hyperframes}/kit/GlassPanel.tsx +1 -1
  55. package/src/{remotion → hyperframes}/kit/Grounds.tsx +1 -1
  56. package/src/{remotion → hyperframes}/kit/Headline.tsx +1 -1
  57. package/src/{remotion → hyperframes}/kit/Hero3D.tsx +1 -1
  58. package/src/{remotion → hyperframes}/kit/HudOverlay.tsx +1 -1
  59. package/src/{remotion → hyperframes}/kit/ImageLayers.tsx +1 -1
  60. package/src/{remotion → hyperframes}/kit/KenBurnsImage.tsx +1 -1
  61. package/src/{remotion → hyperframes}/kit/KeyedClip.tsx +1 -1
  62. package/src/{remotion → hyperframes}/kit/Layers.tsx +1 -1
  63. package/src/{remotion → hyperframes}/kit/LowerThird.tsx +1 -1
  64. package/src/{remotion → hyperframes}/kit/Music.tsx +1 -1
  65. package/src/{remotion → hyperframes}/kit/NamedCursor.tsx +1 -1
  66. package/src/{remotion → hyperframes}/kit/Orbit3D.tsx +1 -1
  67. package/src/{remotion → hyperframes}/kit/Particles3D.tsx +1 -1
  68. package/src/{remotion → hyperframes}/kit/Place.tsx +1 -1
  69. package/src/{remotion → hyperframes}/kit/PromptBox.tsx +1 -1
  70. package/src/{remotion → hyperframes}/kit/Scene3D.tsx +2 -2
  71. package/src/{remotion → hyperframes}/kit/SceneFrame.tsx +1 -1
  72. package/src/{remotion → hyperframes}/kit/ScreenOverlay.tsx +1 -1
  73. package/src/{remotion → hyperframes}/kit/Sfx.tsx +1 -1
  74. package/src/{remotion → hyperframes}/kit/SoundCues.tsx +1 -1
  75. package/src/{remotion → hyperframes}/kit/TerminalLog.tsx +1 -1
  76. package/src/{remotion → hyperframes}/kit/Text3D.tsx +1 -1
  77. package/src/{remotion → hyperframes}/kit/TextOnImage.tsx +1 -1
  78. package/src/{remotion → hyperframes}/kit/TitleCard.tsx +1 -1
  79. package/src/{remotion → hyperframes}/kit/Voiceover.tsx +1 -1
  80. package/src/{remotion → hyperframes}/kit/Warp3D.tsx +1 -1
  81. package/src/hyperframes/kit/brand-transform.ts +25 -0
  82. package/src/{remotion → hyperframes}/kit/docs.ts +6 -2
  83. package/src/{remotion → hyperframes}/kit/index.ts +3 -0
  84. package/src/{remotion → hyperframes}/kit/sound-cues.ts +1 -1
  85. package/src/{remotion → hyperframes}/kit/sound-kinds.ts +14 -1
  86. package/src/{remotion → hyperframes}/kit/theme.ts +42 -40
  87. package/src/{remotion → hyperframes}/kit/ui-math.ts +1 -1
  88. package/src/hyperframes/math.ts +62 -0
  89. package/src/hyperframes/three.tsx +10 -0
  90. package/src/project/chromakey.ts +1 -1
  91. package/src/project/layers.ts +1 -1
  92. package/src/project/serve.ts +2 -2
  93. package/src/render/component-preview.ts +11 -55
  94. package/src/render/render.ts +62 -64
  95. package/src/render/serve.ts +31 -0
  96. package/src/render/static-check.ts +15 -4
  97. package/src/render/validate.ts +4 -4
  98. package/src/render/word-check.ts +1 -1
  99. package/src/render/worker.ts +71 -0
  100. package/src/testing/fixtures.ts +1 -1
  101. package/src/remotion/Root.tsx +0 -31
  102. /package/src/{remotion → hyperframes}/kit/Icon.tsx +0 -0
  103. /package/src/{remotion → hyperframes}/kit/beat.ts +0 -0
  104. /package/src/{remotion → hyperframes}/kit/bg-math.ts +0 -0
  105. /package/src/{remotion → hyperframes}/kit/brand-icons.ts +0 -0
  106. /package/src/{remotion → hyperframes}/kit/caption-groups.ts +0 -0
  107. /package/src/{remotion → hyperframes}/kit/caption-style.ts +0 -0
  108. /package/src/{remotion → hyperframes}/kit/image-layers-math.ts +0 -0
  109. /package/src/{remotion → hyperframes}/kit/inter-bold-typeface.ts +0 -0
  110. /package/src/{remotion → hyperframes}/kit/media.ts +0 -0
  111. /package/src/{remotion → hyperframes}/kit/motion-math.ts +0 -0
  112. /package/src/{remotion → hyperframes}/kit/music-math.ts +0 -0
  113. /package/src/{remotion → hyperframes}/kit/quiet-three.ts +0 -0
  114. /package/src/{remotion → hyperframes}/kit/sample-text.ts +0 -0
  115. /package/src/{remotion → hyperframes}/kit/scene3d-context.ts +0 -0
  116. /package/src/{remotion → hyperframes}/kit/seeded.ts +0 -0
  117. /package/src/{remotion → hyperframes}/kit/three-fx-math.ts +0 -0
  118. /package/src/{remotion → hyperframes}/kit/three-math.ts +0 -0
  119. /package/src/{remotion → hyperframes}/kit/transition-math.ts +0 -0
  120. /package/src/{remotion → hyperframes}/kit/ui-theme.ts +0 -0
  121. /package/src/{remotion → hyperframes}/kit/word-anchor.ts +0 -0
  122. /package/src/{remotion → hyperframes}/types.ts +0 -0
@@ -1,43 +1,44 @@
1
- import { loadFont as loadBody } from "@remotion/google-fonts/Inter";
2
- import { loadFont as loadDisplay } from "@remotion/google-fonts/Sora";
3
- import { loadFont as loadArchivoBlack } from "@remotion/google-fonts/ArchivoBlack";
4
- import { loadFont as loadBebasNeue } from "@remotion/google-fonts/BebasNeue";
5
- import { loadFont as loadGreatVibes } from "@remotion/google-fonts/GreatVibes";
6
- import { loadFont as loadJetBrainsMono } from "@remotion/google-fonts/JetBrainsMono";
7
- import { loadFont as loadKanit } from "@remotion/google-fonts/Kanit";
8
- import { loadFont as loadModak } from "@remotion/google-fonts/Modak";
9
- import { loadFont as loadMontserrat } from "@remotion/google-fonts/Montserrat";
10
- import { loadFont as loadRoboto } from "@remotion/google-fonts/Roboto";
11
- import { loadFont as loadRobotoSlab } from "@remotion/google-fonts/RobotoSlab";
12
- import { loadFont as loadSacramento } from "@remotion/google-fonts/Sacramento";
13
- import { loadFont as loadUnbounded } from "@remotion/google-fonts/Unbounded";
14
- import { loadFont as loadBungeeShade } from "@remotion/google-fonts/BungeeShade";
15
- import { loadFont as loadCinzel } from "@remotion/google-fonts/Cinzel";
16
- import { loadFont as loadCreepster } from "@remotion/google-fonts/Creepster";
17
- import { loadFont as loadHeebo } from "@remotion/google-fonts/Heebo";
18
- import { loadFont as loadKaushanScript } from "@remotion/google-fonts/KaushanScript";
19
- import { loadFont as loadLilitaOne } from "@remotion/google-fonts/LilitaOne";
20
- import { loadFont as loadMrsSaintDelafield } from "@remotion/google-fonts/MrsSaintDelafield";
21
- import { loadFont as loadOrbitron } from "@remotion/google-fonts/Orbitron";
22
- import { loadFont as loadOutfit } from "@remotion/google-fonts/Outfit";
23
- import { loadFont as loadPermanentMarker } from "@remotion/google-fonts/PermanentMarker";
24
- import { loadFont as loadPlayfairDisplay } from "@remotion/google-fonts/PlayfairDisplay";
25
- import { loadFont as loadRubik } from "@remotion/google-fonts/Rubik";
26
- import { loadFont as loadRye } from "@remotion/google-fonts/Rye";
27
- import { loadFont as loadSyne } from "@remotion/google-fonts/Syne";
28
- import { loadFont as loadYellowtail } from "@remotion/google-fonts/Yellowtail";
29
- import { loadFont as loadAlef } from "@remotion/google-fonts/Alef";
30
- import { loadFont as loadAmaticSC } from "@remotion/google-fonts/AmaticSC";
31
- import { loadFont as loadAssistant } from "@remotion/google-fonts/Assistant";
32
- import { loadFont as loadDavidLibre } from "@remotion/google-fonts/DavidLibre";
33
- import { loadFont as loadFrankRuhlLibre } from "@remotion/google-fonts/FrankRuhlLibre";
34
- import { loadFont as loadFredoka } from "@remotion/google-fonts/Fredoka";
35
- import { loadFont as loadKarantina } from "@remotion/google-fonts/Karantina";
36
- import { loadFont as loadNotoSansHebrew } from "@remotion/google-fonts/NotoSansHebrew";
37
- import { loadFont as loadSecularOne } from "@remotion/google-fonts/SecularOne";
38
- import { loadFont as loadSuezOne } from "@remotion/google-fonts/SuezOne";
39
- import { loadFont as loadVarelaRound } from "@remotion/google-fonts/VarelaRound";
40
- import { Easing } from "remotion";
1
+ import { loadFont } from "../fonts";
2
+ const loadBody = (_style: string, _options: unknown) => loadFont("Inter", "inter");
3
+ const loadDisplay = (_style: string, _options: unknown) => loadFont("Sora", "sora");
4
+ const loadArchivoBlack = (_style: string, _options: unknown) => loadFont("Archivo Black", "archivo-black");
5
+ const loadBebasNeue = (_style: string, _options: unknown) => loadFont("Bebas Neue", "bebas-neue");
6
+ const loadGreatVibes = (_style: string, _options: unknown) => loadFont("Great Vibes", "great-vibes");
7
+ const loadJetBrainsMono = (_style: string, _options: unknown) => loadFont("JetBrains Mono", "jetbrains-mono");
8
+ const loadKanit = (_style: string, _options: unknown) => loadFont("Kanit", "kanit");
9
+ const loadModak = (_style: string, _options: unknown) => loadFont("Modak", "modak");
10
+ const loadMontserrat = (_style: string, _options: unknown) => loadFont("Montserrat", "montserrat");
11
+ const loadRoboto = (_style: string, _options: unknown) => loadFont("Roboto", "roboto");
12
+ const loadRobotoSlab = (_style: string, _options: unknown) => loadFont("Roboto Slab", "roboto-slab");
13
+ const loadSacramento = (_style: string, _options: unknown) => loadFont("Sacramento", "sacramento");
14
+ const loadUnbounded = (_style: string, _options: unknown) => loadFont("Unbounded", "unbounded");
15
+ const loadBungeeShade = (_style: string, _options: unknown) => loadFont("Bungee Shade", "bungee-shade");
16
+ const loadCinzel = (_style: string, _options: unknown) => loadFont("Cinzel", "cinzel");
17
+ const loadCreepster = (_style: string, _options: unknown) => loadFont("Creepster", "creepster");
18
+ const loadHeebo = (_style: string, _options: unknown) => loadFont("Heebo", "heebo");
19
+ const loadKaushanScript = (_style: string, _options: unknown) => loadFont("Kaushan Script", "kaushan-script");
20
+ const loadLilitaOne = (_style: string, _options: unknown) => loadFont("Lilita One", "lilita-one");
21
+ const loadMrsSaintDelafield = (_style: string, _options: unknown) => loadFont("Mrs Saint Delafield", "mrs-saint-delafield");
22
+ const loadOrbitron = (_style: string, _options: unknown) => loadFont("Orbitron", "orbitron");
23
+ const loadOutfit = (_style: string, _options: unknown) => loadFont("Outfit", "outfit");
24
+ const loadPermanentMarker = (_style: string, _options: unknown) => loadFont("Permanent Marker", "permanent-marker");
25
+ const loadPlayfairDisplay = (_style: string, _options: unknown) => loadFont("Playfair Display", "playfair-display");
26
+ const loadRubik = (_style: string, _options: unknown) => loadFont("Rubik", "rubik");
27
+ const loadRye = (_style: string, _options: unknown) => loadFont("Rye", "rye");
28
+ const loadSyne = (_style: string, _options: unknown) => loadFont("Syne", "syne");
29
+ const loadYellowtail = (_style: string, _options: unknown) => loadFont("Yellowtail", "yellowtail");
30
+ const loadAlef = (_style: string, _options: unknown) => loadFont("Alef", "alef");
31
+ const loadAmaticSC = (_style: string, _options: unknown) => loadFont("Amatic SC", "amatic-sc");
32
+ const loadAssistant = (_style: string, _options: unknown) => loadFont("Assistant", "assistant");
33
+ const loadDavidLibre = (_style: string, _options: unknown) => loadFont("David Libre", "david-libre");
34
+ const loadFrankRuhlLibre = (_style: string, _options: unknown) => loadFont("Frank Ruhl Libre", "frank-ruhl-libre");
35
+ const loadFredoka = (_style: string, _options: unknown) => loadFont("Fredoka", "fredoka");
36
+ const loadKarantina = (_style: string, _options: unknown) => loadFont("Karantina", "karantina");
37
+ const loadNotoSansHebrew = (_style: string, _options: unknown) => loadFont("Noto Sans Hebrew", "noto-sans-hebrew");
38
+ const loadSecularOne = (_style: string, _options: unknown) => loadFont("Secular One", "secular-one");
39
+ const loadSuezOne = (_style: string, _options: unknown) => loadFont("Suez One", "suez-one");
40
+ const loadVarelaRound = (_style: string, _options: unknown) => loadFont("Varela Round", "varela-round");
41
+ import { Easing } from "../frame";
41
42
 
42
43
  // Loaded once for every video. Hero text must never fall back to a system default font.
43
44
  const display = loadDisplay("normal", { weights: ["600", "700", "800"], subsets: ["latin"] });
@@ -98,6 +99,7 @@ const loaders = {
98
99
  amaticSC: () => loadAmaticSC("normal", { weights: ["400", "700"], ...hebrew }), // hand-lettered capitals
99
100
  };
100
101
  export type KitFont = keyof typeof loaders;
102
+ export const KIT_FONTS = Object.keys(loaders) as KitFont[];
101
103
  const loaded = new Map<KitFont, string>();
102
104
  export function font(name: KitFont): string {
103
105
  let family = loaded.get(name);
@@ -2,7 +2,7 @@
2
2
  // mount: the frame useCurrentFrame() gives where the component stands (inside a SceneFrame, the scene's own frame; inside a Sequence, the sequence's).
3
3
  // Each component has a companion function here that takes the SAME props and returns the frames of its events, in that same clock, so a sound can be
4
4
  // placed on them: add the scene's startFrame (or pass it as `offset` to cuesFor) to make them absolute.
5
- import { measureSpring } from "remotion";
5
+ import { measureSpring } from "../frame";
6
6
 
7
7
  export const clamp01 = (n: number) => (n > 0 ? (n < 1 ? n : 1) : 0);
8
8
  export const outCubic = (t: number) => 1 - (1 - clamp01(t)) ** 3;
@@ -0,0 +1,62 @@
1
+ // Seekable animation math. These functions have no browser or React dependency.
2
+ export type SpringConfig = { damping?: number; stiffness?: number; mass?: number; overshootClamping?: boolean };
3
+ export function spring({ frame, fps, config = {}, from = 0, to = 1, delay = 0, durationInFrames, durationRestThreshold = 0.005, reverse = false }: { frame: number; fps: number; config?: SpringConfig; from?: number; to?: number; delay?: number; durationInFrames?: number; durationRestThreshold?: number; reverse?: boolean }): number {
4
+ const length = durationInFrames ?? measureSpring({ fps, config, threshold: durationRestThreshold });
5
+ let f = frame - delay;
6
+ if (reverse) f = length - f;
7
+ if (f <= 0) return from;
8
+ if (durationInFrames && f >= durationInFrames) return to;
9
+ const t = f / fps * (durationInFrames ? measureSpring({ fps, config, threshold: durationRestThreshold }) / durationInFrames : 1);
10
+ const m = config.mass ?? 1, k = config.stiffness ?? 100, c = config.damping ?? 10;
11
+ const w = Math.sqrt(k / m), z = c / (2 * Math.sqrt(k * m));
12
+ let p: number;
13
+ if (z < 1) { const wd = w * Math.sqrt(1 - z * z); p = 1 - Math.exp(-z * w * t) * (Math.cos(wd * t) + z * w / wd * Math.sin(wd * t)); }
14
+ else if (z === 1) p = 1 - (1 + w * t) * Math.exp(-w * t);
15
+ else { const r = Math.sqrt(z * z - 1), a = -w * (z - r), b = -w * (z + r); p = 1 - (b * Math.exp(a * t) - a * Math.exp(b * t)) / (b - a); }
16
+ return from + (to - from) * (config.overshootClamping ? Math.min(1, p) : p);
17
+ }
18
+ export function measureSpring({ fps, config = {}, threshold = 0.005 }: { fps: number; config?: SpringConfig; threshold?: number }): number {
19
+ const m = config.mass ?? 1, k = config.stiffness ?? 100, c = config.damping ?? 10;
20
+ const w = Math.sqrt(k / m), z = c / (2 * Math.sqrt(k * m));
21
+ // Require a full second inside the rest band so an oscillation's zero crossing cannot end the spring.
22
+ let last = 0;
23
+ for (let f = 0; f < fps * 60; f++) {
24
+ const t = f / fps;
25
+ let error: number;
26
+ if (z < 1) { const wd = w * Math.sqrt(1 - z * z); error = Math.exp(-z * w * t) * (Math.cos(wd * t) + z * w / wd * Math.sin(wd * t)); }
27
+ else if (z === 1) error = (1 + w * t) * Math.exp(-w * t);
28
+ else { const r = Math.sqrt(z * z - 1), a = -w * (z - r), b = -w * (z + r); error = (b * Math.exp(a * t) - a * Math.exp(b * t)) / (b - a); }
29
+ if (Math.abs(error) > threshold) last = f;
30
+ if (f - last >= fps) return last + 1;
31
+ }
32
+ throw new Error("Spring did not settle within 60 seconds.");
33
+ }
34
+ type Extrapolate = "extend" | "clamp" | "identity" | "wrap";
35
+ export function interpolate(value: number, input: readonly number[], output: readonly number[], options: { easing?: (t: number) => number; extrapolateLeft?: Extrapolate; extrapolateRight?: Extrapolate } = {}): number {
36
+ if (input.length !== output.length || input.length < 2 || input.some((n, i) => i > 0 && n <= input[i - 1]!)) throw new Error("Interpolation ranges must have equal length and increasing input values.");
37
+ const lo = input[0]!, hi = input[input.length - 1]!;
38
+ if (value < lo || value > hi) { const mode = value < lo ? options.extrapolateLeft : options.extrapolateRight; if (mode === "identity") return value; if (mode === "clamp") value = Math.min(hi, Math.max(lo, value)); if (mode === "wrap") value = lo + ((value - lo) % (hi - lo) + hi - lo) % (hi - lo); }
39
+ let i = 0; while (i < input.length - 2 && value > input[i + 1]!) i++;
40
+ const t = (value - input[i]!) / (input[i + 1]! - input[i]!);
41
+ return output[i]! + (output[i + 1]! - output[i]!) * (options.easing ?? Easing.linear)(t);
42
+ }
43
+ export const Easing = {
44
+ linear: (t: number) => t, quad: (t: number) => t * t, cubic: (t: number) => t ** 3,
45
+ sin: (t: number) => 1 - Math.cos(t * Math.PI / 2), exp: (t: number) => t === 0 ? 0 : 2 ** (10 * (t - 1)),
46
+ in: (fn: (t: number) => number) => fn,
47
+ out: (fn: (t: number) => number) => (t: number) => 1 - fn(1 - t),
48
+ inOut: (fn: (t: number) => number) => (t: number) => t < 0.5 ? fn(t * 2) / 2 : 1 - fn((1 - t) * 2) / 2,
49
+ bezier: (x1: number, y1: number, x2: number, y2: number) => (t: number) => {
50
+ if (t <= 0 || t >= 1) return t;
51
+ const curve = (u: number, a: number, b: number) => 3 * (1 - u) ** 2 * u * a + 3 * (1 - u) * u * u * b + u ** 3;
52
+ let lo = 0, hi = 1;
53
+ for (let i = 0; i < 40; i++) { const u = (lo + hi) / 2; if (curve(u, x1, x2) < t) lo = u; else hi = u; }
54
+ return curve((lo + hi) / 2, y1, y2);
55
+ },
56
+ };
57
+ export function random(seed: string | number): number {
58
+ let h = 2166136261;
59
+ for (const c of String(seed)) h = Math.imul(h ^ c.charCodeAt(0), 16777619);
60
+ h ^= h >>> 16; h = Math.imul(h, 0x7feb352d); h ^= h >>> 15; h = Math.imul(h, 0x846ca68b);
61
+ return ((h ^ h >>> 16) >>> 0) / 4294967296;
62
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { Canvas, useThree, type CanvasProps } from "@react-three/fiber";
3
+ import { FrameContext, useCurrentFrame, delayRender, continueRender } from "./frame";
4
+ const Draw: React.FC = () => { const f = useCurrentFrame(), s = useThree(); React.useLayoutEffect(() => { s.invalidate(); }, [f, s]); return null; };
5
+ export const ThreeCanvas: React.FC<CanvasProps & { width: number; height: number }> = ({ width, height, children, style, ...props }) => {
6
+ const state = React.useContext(FrameContext);
7
+ const [handle] = React.useState(() => delayRender("3D canvas"));
8
+ React.useEffect(() => () => continueRender(handle), [handle]);
9
+ return <Canvas {...props} onCreated={() => continueRender(handle)} frameloop="demand" gl={{ preserveDrawingBuffer: true, alpha: true }} style={{ width, height, ...style }}><FrameContext.Provider value={state}>{children}<Draw /></FrameContext.Provider></Canvas>;
10
+ };
@@ -41,7 +41,7 @@ async function cornerGreen(input: string): Promise<{ colour: string; saturated:
41
41
  return { colour: `0x${[r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("")}`, saturated: g - Math.max(r, b) >= 60 };
42
42
  }
43
43
 
44
- // Turns the green of a green-screen clip into transparency: VP9 with an alpha channel, which Remotion plays with `transparent`.
44
+ // Turns the green of a green-screen clip into transparency: VP9 with an alpha channel, which HyperFrames plays with `transparent`.
45
45
  // The key is on the green found in the frame's corners, with some give for noise and uneven light; the despill pulls the green glow
46
46
  // off the subject's edges. The original is left alone.
47
47
  export async function keyGreen(input: string, output: string, opts: KeyOptions = {}): Promise<void> {
@@ -3,7 +3,7 @@ import { mkdtempSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { promisify } from "node:util";
6
- import { coverageOf, subjectBoxOf, textZoneOf, type ImageLayersRecord, type Rect, type TextZone } from "../remotion/kit/image-layers-math";
6
+ import { coverageOf, subjectBoxOf, textZoneOf, type ImageLayersRecord, type Rect, type TextZone } from "../hyperframes/kit/image-layers-math";
7
7
 
8
8
  const run = promisify(execFile);
9
9
  const GRID = 48;
@@ -12,9 +12,9 @@ const TYPES: Record<string, string> = {
12
12
 
13
13
  export type MediaServer = { url(rel: string): string; close(): Promise<void> };
14
14
 
15
- // Remotion loads media over HTTP, so the project's media is served on a local port while a build runs.
15
+ // HyperFrames loads media over HTTP, so the project's media is served on a local port while a build runs.
16
16
  // Only files under <project>/assets/ are served, never .json, and only under a random per-run path prefix: access-control-allow-origin
17
- // has to be * for Remotion's renderer, so the unguessable prefix is what keeps other web pages from reading the files.
17
+ // has to be * for HyperFrames's renderer, so the unguessable prefix is what keeps other web pages from reading the files.
18
18
  export async function serveDir(root: string): Promise<MediaServer> {
19
19
  const assets = join(resolve(root), "assets");
20
20
  const prefix = randomBytes(16).toString("hex");
@@ -1,65 +1,21 @@
1
- import { execFile } from "node:child_process";
2
- import { mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
2
  import { tmpdir } from "node:os";
4
3
  import { dirname, join } from "node:path";
5
- import { promisify } from "node:util";
6
- import { renderMedia, selectComposition } from "@remotion/renderer";
7
- import { bundleProject, disposeBundle } from "./render";
8
-
9
- const run = promisify(execFile);
10
- const NAME = /^[A-Z][A-Za-z0-9]*$/;
11
-
4
+ import { bundleProject, disposeBundle, renderVideo } from "./render";
5
+ import { migrateFrameImports } from "./static-check";
12
6
  export const PREVIEW = { width: 960, height: 600, fps: 30, frames: 105 } as const;
13
-
14
- // The generated entry: the kit's default dark background with the example filling the frame. Importing the kit also loads
15
- // its fonts, which is what makes text render in the kit fonts. `name` is checked against NAME and `example` starts with `<name`,
16
- // because both are interpolated into this code; the example is trusted seed text from the owner's own repository.
17
- const entrySource = (name: string, example: string) => `import React from "react";
18
- import { AbsoluteFill, Composition, registerRoot } from "remotion";
19
- import { palettes } from "reelkit/kit";
20
- import { ${name} } from "./${name}";
21
-
22
- void ${name};
23
- const Preview: React.FC = () => (
24
- <AbsoluteFill style={{ background: palettes.darkTech.bg }}>
25
- ${example}
26
- </AbsoluteFill>
27
- );
28
-
29
- registerRoot(() => (
30
- <Composition id="Preview" component={Preview} durationInFrames={${PREVIEW.frames}} fps={${PREVIEW.fps}} width={${PREVIEW.width}} height={${PREVIEW.height}} />
31
- ));
32
- `;
33
-
34
- // Renders one component with its example to a short silent MP4 (960x600, 30 fps, 3.5 s, H.264, yuv420p, CRF 28).
35
- // Everything it creates besides outPath (the temp folder and the bundle) is removed again, also when it fails.
7
+ const NAME = /^[A-Z][A-Za-z0-9]*$/;
36
8
  export async function renderComponentPreview(input: { name: string; source: string; example: string }, outPath: string): Promise<void> {
37
9
  const { name, source, example } = input;
38
10
  if (!NAME.test(name)) throw new Error(`Component name "${name}" must be PascalCase letters and digits.`);
39
11
  if (!example.trimStart().startsWith(`<${name}`)) throw new Error(`The example for ${name} must start with <${name}.`);
40
-
41
- const work = await mkdtemp(join(tmpdir(), "rk-cmp-preview-"));
42
- let serveUrl: string | undefined;
12
+ const work = await mkdtemp(join(tmpdir(), "rk-cmp-preview-")); let bundle: string | undefined;
13
+ const props = { manifest: { width: PREVIEW.width, height: PREVIEW.height, fps: PREVIEW.fps, totalFrames: PREVIEW.frames, scenes: [] }, urls: {} };
43
14
  try {
44
- await writeFile(join(work, `${name}.tsx`), source);
15
+ await writeFile(join(work, `${name}.tsx`), migrateFrameImports(source));
45
16
  const entry = join(work, "index.tsx");
46
- await writeFile(entry, entrySource(name, example.trim()));
47
- serveUrl = await bundleProject(entry);
48
- const composition = await selectComposition({ serveUrl, id: "Preview" });
49
- const rendered = join(work, "rendered.mp4");
50
- await renderMedia({
51
- composition, serveUrl, codec: "h264", outputLocation: rendered, crf: 28, pixelFormat: "yuv420p", colorSpace: "bt709", muted: true, logLevel: "error",
52
- });
53
- // The last pass guarantees no audio stream and the index at the front, whatever Remotion wrote; the video is copied, not re-encoded.
54
- const finished = join(work, "final.mp4");
55
- await run("ffmpeg", ["-y", "-v", "error", "-i", rendered, "-an", "-movflags", "+faststart", "-c:v", "copy", finished]);
56
- await mkdir(dirname(outPath), { recursive: true });
57
- await rename(finished, outPath).catch(async () => {
58
- // A different file system than the temp folder: copy instead.
59
- await run("ffmpeg", ["-y", "-v", "error", "-i", finished, "-c", "copy", "-movflags", "+faststart", outPath]);
60
- });
61
- } finally {
62
- if (serveUrl) await disposeBundle(serveUrl).catch(() => {});
63
- await rm(work, { recursive: true, force: true });
64
- }
17
+ await writeFile(entry, `import React from "react";\nimport { AbsoluteFill } from "reelkit/frame";\nimport { mountVideo } from "reelkit/host";\nimport { palettes } from "reelkit/kit";\nimport { ${name} } from "./${name}";\nmountVideo(() => <AbsoluteFill style={{background: palettes.darkTech.bg}}>${example.trim()}</AbsoluteFill>);\n`);
18
+ bundle = await bundleProject(entry); await mkdir(dirname(outPath), { recursive: true });
19
+ await renderVideo(bundle, props, outPath, { silent: true, crf: 28 });
20
+ } finally { if (bundle) await disposeBundle(bundle).catch(() => {}); await rm(work, { recursive: true, force: true }); }
65
21
  }
@@ -1,81 +1,79 @@
1
- import { mkdir, rm, writeFile } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
3
- import { bundle } from "@remotion/bundler";
4
- import { ensureBrowser, renderMedia, renderStill, selectComposition } from "@remotion/renderer";
5
- import type { VideoProps } from "../remotion/types";
1
+ import { execFile } from "node:child_process";
2
+ import { cp, mkdir, mkdtemp, rm, writeFile, readdir, readFile } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { dirname, join, basename } from "node:path";
6
+ import { promisify } from "node:util";
7
+ import { build } from "esbuild";
8
+ import type { VideoProps } from "../hyperframes/types";
9
+ import { fontFamilies } from "../hyperframes/fonts";
6
10
  import { packageDir } from "./deps";
7
- import { KIT_INDEX, ROOT_FILE } from "./validate";
11
+ import { KIT_INDEX, PKG_ROOT } from "./validate";
8
12
 
9
- const ENTRY_SOURCE = `import { registerRoot } from "remotion";
10
- import { makeRoot } from "reelkit/root";
11
- import { Video } from "../../src/Video";
12
-
13
- registerRoot(makeRoot(Video));
14
- `;
15
-
16
- // The project folder has no node_modules. Each dependency is pinned to the one copy installed with this package, so the
17
- // kit, Root and the user's composition all share a single react and remotion (a second react breaks hooks), and the 3D layer a single three
18
- // (two copies of three break instanceof checks). User code still may not import them: only the kit does.
13
+ const run = promisify(execFile);
14
+ const HOST = join(PKG_ROOT, "src/hyperframes/host.tsx");
15
+ const FRAME = join(PKG_ROOT, "src/hyperframes/frame.tsx");
19
16
  export function dependencyAliases(): Record<string, string> {
20
- return Object.fromEntries(["react", "react-dom", "remotion", "three", "@react-three/fiber", "@remotion/three"].map((n) => [n, packageDir(n)]));
17
+ return Object.fromEntries(["react", "react-dom", "three", "@react-three/fiber"].map((name) => [name, packageDir(name)]));
21
18
  }
22
-
23
- // Writes the bundler's entry file into the project. The composition itself stays where the user's Claude wrote it.
24
19
  export async function writeEntry(projectDir: string): Promise<string> {
25
20
  const dir = join(projectDir, ".reelkit", "build");
26
- await rm(dir, { recursive: true, force: true });
27
- await mkdir(dir, { recursive: true });
21
+ await rm(dir, { recursive: true, force: true }); await mkdir(dir, { recursive: true });
28
22
  const entry = join(dir, "index.ts");
29
- await writeFile(entry, ENTRY_SOURCE);
23
+ await writeFile(entry, 'import { mountVideo } from "reelkit/host";\nimport { Video } from "../../src/Video";\nmountVideo(Video);\n');
30
24
  return entry;
31
25
  }
32
-
33
- // Webpack's on-disk cache would only grow, by tens of megabytes per render. It is switched off, and each bundle
34
- // is deleted by disposeBundle once it has been used.
26
+ // A self-contained HyperFrames page using the package's copies of React and Three.
35
27
  export async function bundleProject(entry: string): Promise<string> {
36
- return bundle({
37
- entryPoint: entry,
38
- enableCaching: false,
39
- webpackOverride: (config) => ({
40
- ...config,
41
- resolve: {
42
- ...config.resolve,
43
- alias: { ...(config.resolve?.alias as Record<string, string> | undefined), ...dependencyAliases(), "reelkit/kit": KIT_INDEX, "reelkit/root": ROOT_FILE },
44
- },
45
- }),
46
- });
28
+ const dir = await mkdtemp(join(tmpdir(), "reelkit-hyperframes-"));
29
+ try {
30
+ await build({ entryPoints: [entry], bundle: true, outfile: join(dir, "composition.js"), platform: "browser", format: "iife", jsx: "automatic", logLevel: "silent", loader: { ".woff2": "file", ".woff": "file", ".ttf": "file" }, define: { "process.env.NODE_ENV": '"production"' }, alias: { ...dependencyAliases(), "reelkit/kit": KIT_INDEX, "reelkit/frame": FRAME, "reelkit/host": HOST } });
31
+ await mkdir(join(dir, "fonts"));
32
+ for (const slug of Object.values(fontFamilies)) {
33
+ const source = packageDir(`@fontsource/${slug}`);
34
+ await cp(join(source, "files"), join(dir, "fonts", "files"), { recursive: true });
35
+ const sheets = (await readdir(source)).filter((name) => /^\d+(?:-italic)?\.css$/.test(name));
36
+ await writeFile(join(dir, "fonts", `${slug}.css`), (await Promise.all(sheets.map((name) => readFile(join(source, name), "utf8")))).join("\n"));
37
+ }
38
+ await writeFile(join(dir, "index.html"), '<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;overflow:hidden}#stage{position:relative;overflow:hidden}</style></head><body><div id="stage"></div><script src="props.js"></script><script src="composition.js"></script></body></html>');
39
+ return dir;
40
+ } catch (error) { await rm(dir, { recursive: true, force: true }); throw error; }
47
41
  }
48
-
49
- export async function disposeBundle(serveUrl: string): Promise<void> {
50
- // Only ever remove a folder that is one of Remotion's own temporary bundles.
51
- if (/remotion-webpack-bundle-/.test(serveUrl)) await rm(serveUrl, { recursive: true, force: true });
42
+ export async function disposeBundle(dir: string): Promise<void> {
43
+ if (dirname(dir) === tmpdir() && basename(dir).startsWith("reelkit-hyperframes-")) await rm(dir, { recursive: true, force: true });
52
44
  }
53
-
54
- // Makes sure Remotion's headless browser is installed. onDownload is called once, only when a download is about to begin.
55
- // Remotion's own progress bar is replaced because it writes to stdout, which --json keeps for the result alone.
56
45
  export type EnsureBrowser = (onDownload: () => void) => Promise<void>;
46
+ export const GL_BACKEND = "software" as const;
47
+ export type RenderOptions = { uses3D?: boolean; silent?: boolean; crf?: number };
48
+ export function browserPath(): string | undefined {
49
+ if (process.env.REELKIT_CHROME_PATH) return process.env.REELKIT_CHROME_PATH;
50
+ const paths = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium"] : process.platform === "linux" ? ["/usr/bin/chromium", "/usr/bin/chromium-browser", "/usr/bin/google-chrome"] : [];
51
+ return paths.find(existsSync);
52
+ }
57
53
  export const ensureRenderBrowser: EnsureBrowser = async (onDownload) => {
58
- await ensureBrowser({ onBrowserDownload: () => { onDownload(); return { version: null, onProgress: () => {} }; } });
54
+ if (browserPath()) return;
55
+ const { resolveHeadlessShellPath } = await import("@hyperframes/engine");
56
+ const cached = resolveHeadlessShellPath({}) ?? resolveHeadlessShellPath({ chromePath: undefined });
57
+ if (cached) { process.env.REELKIT_CHROME_PATH = cached; return; }
58
+ const { install, Browser, resolveBuildId, detectBrowserPlatform, getInstalledBrowsers } = await import("@puppeteer/browsers");
59
+ const installed = (await getInstalledBrowsers({ cacheDir: join(PKG_ROOT, ".browser") })).find((b) => b.browser === Browser.CHROMEHEADLESSSHELL && existsSync(b.executablePath));
60
+ if (installed) { process.env.REELKIT_CHROME_PATH = installed.executablePath; return; }
61
+ const platform = detectBrowserPlatform(); if (!platform) throw new Error("No supported Chrome platform. Set REELKIT_CHROME_PATH.");
62
+ onDownload();
63
+ const browser = await install({ browser: Browser.CHROMEHEADLESSSHELL, buildId: await resolveBuildId(Browser.CHROMEHEADLESSSHELL, platform, "stable"), cacheDir: join(PKG_ROOT, ".browser"), platform });
64
+ process.env.REELKIT_CHROME_PATH = browser.executablePath;
59
65
  };
60
-
61
- // Remotion's headless browser has no GPU, so three.js needs a software GL. "angle" is the setting that draws WebGL here; it is used only for a film that
62
- // has a 3D scene, so every 2D render keeps the browser's own defaults and looks exactly as before. This is the one place that says it.
63
- export const GL_BACKEND = "angle" as const;
64
- export type RenderOptions = { uses3D?: boolean };
65
- const chromium = (o?: RenderOptions) => (o?.uses3D ? { chromiumOptions: { gl: GL_BACKEND } } : {});
66
-
67
- const asInput = (props: VideoProps) => props as unknown as Record<string, unknown>;
68
-
69
- export async function renderStills(serveUrl: string, props: VideoProps, frames: number[], outDir: string, options?: RenderOptions) {
70
- await mkdir(outDir, { recursive: true });
71
- const composition = await selectComposition({ serveUrl, id: "Video", inputProps: asInput(props), ...chromium(options) });
72
- for (const frame of frames) {
73
- await renderStill({ composition, serveUrl, frame, output: join(outDir, `still-${frame}.png`), inputProps: asInput(props), ...chromium(options) });
74
- }
66
+ async function render(dir: string, props: VideoProps, mode: "stills" | "video", output: string, frames: number[], options?: RenderOptions) {
67
+ await ensureRenderBrowser(() => {});
68
+ await writeFile(join(dir, "props.js"), `window.__reelkitProps=${JSON.stringify(props).replace(/</g, "\\u003c")};`);
69
+ // Isolate engine diagnostics from the CLI's JSON stdout.
70
+ const request = join(dir, "render-request.json");
71
+ await writeFile(request, JSON.stringify({ dir, props, mode, output, frames, options, chromePath: browserPath() }));
72
+ await run(process.execPath, ["--import", join(packageDir("tsx"), "dist/loader.mjs"), join(PKG_ROOT, "src/render/worker.ts"), request], { maxBuffer: 16 * 1024 * 1024 });
75
73
  }
76
-
77
- export async function renderVideo(serveUrl: string, props: VideoProps, outPath: string, options?: RenderOptions) {
78
- await mkdir(dirname(outPath), { recursive: true });
79
- const composition = await selectComposition({ serveUrl, id: "Video", inputProps: asInput(props), ...chromium(options) });
80
- await renderMedia({ composition, serveUrl, codec: "h264", outputLocation: outPath, inputProps: asInput(props), ...chromium(options) });
74
+ export async function renderStills(dir: string, props: VideoProps, frames: number[], outDir: string, options?: RenderOptions) {
75
+ await mkdir(outDir, { recursive: true }); await render(dir, props, "stills", outDir, frames, options);
76
+ }
77
+ export async function renderVideo(dir: string, props: VideoProps, outPath: string, options?: RenderOptions) {
78
+ await mkdir(dirname(outPath), { recursive: true }); await render(dir, props, "video", outPath, [], options);
81
79
  }
@@ -0,0 +1,31 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { createReadStream, statSync } from "node:fs";
3
+ import { createServer } from "node:http";
4
+ import type { AddressInfo } from "node:net";
5
+ import { extname, resolve, sep } from "node:path";
6
+
7
+ const TYPES: Record<string, string> = {
8
+ ".html": "text/html; charset=utf-8", ".js": "application/javascript", ".css": "text/css",
9
+ ".json": "application/json", ".woff2": "font/woff2", ".woff": "font/woff", ".ttf": "font/ttf",
10
+ ".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml",
11
+ };
12
+
13
+ // Serve the generated composition only to localhost, under an unguessable per-render prefix.
14
+ // The generic HyperFrames file server listens on all interfaces; project copy and media URLs stay private here.
15
+ export async function serveComposition(dir: string): Promise<{ url: string; close(): Promise<void> }> {
16
+ const root = resolve(dir), prefix = `/${randomBytes(16).toString("hex")}/`;
17
+ const server = createServer((req, res) => {
18
+ let path: string;
19
+ try { path = decodeURIComponent((req.url ?? "").split("?")[0]!); } catch { res.writeHead(400).end(); return; }
20
+ if (!path.startsWith(prefix)) { res.writeHead(404).end(); return; }
21
+ const file = resolve(root, path.slice(prefix.length) || "index.html");
22
+ try {
23
+ if (!file.startsWith(root + sep) || !statSync(file).isFile()) { res.writeHead(404).end(); return; }
24
+ res.writeHead(200, { "content-type": TYPES[extname(file)] ?? "application/octet-stream", "cache-control": "no-store" });
25
+ const stream = createReadStream(file); stream.on("error", () => res.destroy()); stream.pipe(res);
26
+ } catch { res.writeHead(404).end(); }
27
+ });
28
+ await new Promise<void>((ready, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", ready); });
29
+ const port = (server.address() as AddressInfo).port;
30
+ return { url: `http://127.0.0.1:${port}${prefix}`, close: () => new Promise<void>((ready) => { server.closeAllConnections(); server.close(() => ready()); }) };
31
+ }
@@ -7,8 +7,19 @@ export const MAIN_FILE = "Video.tsx";
7
7
  export const FILE_NAME = /^[A-Z][A-Za-z0-9]*\.tsx$/;
8
8
  export const componentName = (file: string) => file.replace(/\.tsx$/, "");
9
9
 
10
+ // Upgrade previously published library components without touching on-screen copy or comments.
11
+ export function migrateFrameImports(source: string): string {
12
+ const parsed = sourceFile(source);
13
+ const edits: { start: number; end: number }[] = [];
14
+ for (const node of parsed.statements) {
15
+ if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "remotion") edits.push({ start: node.moduleSpecifier.getStart(parsed), end: node.moduleSpecifier.getEnd() });
16
+ }
17
+ for (const edit of edits.reverse()) source = source.slice(0, edit.start) + '"reelkit/frame"' + source.slice(edit.end);
18
+ return source;
19
+ }
20
+
10
21
  export const KIT = "reelkit/kit";
11
- const BASE_IMPORTS = new Set(["react", "remotion", KIT]);
22
+ const BASE_IMPORTS = new Set(["react", "reelkit/frame", KIT]);
12
23
 
13
24
  // This check keeps honest compositions deterministic and self-contained. It is NOT a security boundary:
14
25
  // code in src/ runs on the user's machine when it is bundled and rendered, and anyone determined can get around a list of names.
@@ -77,7 +88,7 @@ function inspectSource(source: string, file?: string): Finding {
77
88
  return Boolean(x && ts.isIdentifier(x) && x.text === name && !declared(x));
78
89
  };
79
90
  const flagMember = (object: ts.Expression | undefined, member: string | undefined) => {
80
- if (isGlobal(object, "Math") && member === "random") forbidden.add("Math.random (use random(seed) from remotion)");
91
+ if (isGlobal(object, "Math") && member === "random") forbidden.add("Math.random (use random(seed) from reelkit/frame)");
81
92
  if (isGlobal(object, "Date") && member === "now") forbidden.add("the clock (Date)");
82
93
  };
83
94
  // The key a destructuring element reads: `random` in `{ random }` and in `{ random: r }`, "random" in `{ ["random"]: r }`.
@@ -140,8 +151,8 @@ export function staticCheck(source: string, file: string, siblings: string[]): s
140
151
  if (BASE_IMPORTS.has(s) || allowedSiblings.has(s)) continue;
141
152
  errors.push(
142
153
  isMain
143
- ? `${file}: import not allowed: "${s}". Use react, remotion, ${KIT}, or a component file that exists ("./Name").`
144
- : `${file}: import not allowed: "${s}". A component may only import react, remotion and ${KIT}.`,
154
+ ? `${file}: import not allowed: "${s}". Use react, reelkit/frame, ${KIT}, or a component file that exists ("./Name").`
155
+ : `${file}: import not allowed: "${s}". A component may only import react, reelkit/frame and ${KIT}.`,
145
156
  );
146
157
  }
147
158
  for (const name of found.forbidden) errors.push(`${file}: not allowed: ${name}.`);
@@ -4,15 +4,15 @@ import ts from "typescript";
4
4
  import { packageDir } from "./deps";
5
5
 
6
6
  export const PKG_ROOT = fileURLToPath(new URL("../..", import.meta.url));
7
- export const KIT_INDEX = join(PKG_ROOT, "src", "remotion", "kit", "index.ts");
8
- export const ROOT_FILE = join(PKG_ROOT, "src", "remotion", "Root.tsx");
7
+ export const KIT_INDEX = join(PKG_ROOT, "src", "hyperframes", "kit", "index.ts");
8
+ export const ROOT_FILE = join(PKG_ROOT, "src", "hyperframes", "Root.tsx");
9
9
 
10
10
  // The static check lives in its own module, which a server can bundle; everything it exports is available from here as before.
11
11
  export { FILE_NAME, MAIN_FILE, componentName, importSpecifiers, staticCheck } from "./static-check";
12
12
  import { KIT, sourceFile, unwrap } from "./static-check";
13
13
 
14
14
  const typesReact = packageDir("@types/react");
15
- export const remotionDir = packageDir("remotion");
15
+ export const frameFile = join(PKG_ROOT, "src", "hyperframes", "frame.tsx");
16
16
 
17
17
  export function typecheck(files: string[]): string[] {
18
18
  const program = ts.createProgram(files, {
@@ -30,7 +30,7 @@ export function typecheck(files: string[]): string[] {
30
30
  [KIT]: [KIT_INDEX],
31
31
  react: [typesReact],
32
32
  "react/*": [join(typesReact, "*")],
33
- remotion: [remotionDir],
33
+ "reelkit/frame": [frameFile],
34
34
  },
35
35
  });
36
36
  const wanted = new Set(files);
@@ -2,7 +2,7 @@
2
2
  // the word is in that scene's narration. Pure text in, findings out (the TypeScript syntax tree, never regular expressions on code), so `check`
3
3
  // and `preview` can both use it.
4
4
  import ts from "typescript";
5
- import { normalizeWord, wordFrame, type WordScene } from "../remotion/kit/word-anchor";
5
+ import { normalizeWord, wordFrame, type WordScene } from "../hyperframes/kit/word-anchor";
6
6
  import { sourceFile, unwrap } from "./static-check";
7
7
 
8
8
  export const WORD_HELPERS = ["wordFrame", "wordFrames", "onWord", "onWordBeat"] as const;