@vibecuting/video-project-core 0.1.29 → 0.1.31

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 (182) hide show
  1. package/package.json +9 -5
  2. package/src/base/BaseRemotionScene.test.tsx +11 -1
  3. package/src/base/index.ts +1 -0
  4. package/src/base/utils/asset-source.ts +11 -0
  5. package/src/base/utils/background-audio-layer.tsx +90 -0
  6. package/src/base/utils/background-layer.tsx +84 -0
  7. package/src/base/utils/chapter-progress-foreground.tsx +125 -0
  8. package/src/base/utils/chapter-progress-state.test.ts +28 -0
  9. package/src/base/utils/chapter-progress-state.ts +33 -0
  10. package/src/base/utils/display-motion.test.ts +21 -0
  11. package/src/base/utils/display-motion.ts +52 -0
  12. package/src/base/utils/display-surface.ts +57 -0
  13. package/src/base/utils/index.ts +10 -0
  14. package/src/base/utils/subtitle-style.ts +28 -0
  15. package/src/base/utils/video-layer-types.test.ts +145 -0
  16. package/src/base/utils/video-layer-types.ts +220 -0
  17. package/src/base/utils/voice-captions-foreground.tsx +169 -0
  18. package/src/core/intro/index.ts +18 -18
  19. package/src/core/outro/index.ts +33 -33
  20. package/src/core/scene/2dscene/{default-2d-scene-Horizontal.stories.tsx → default-2d-scene/default-2d-scene-Horizontal.stories.tsx} +1 -1
  21. package/src/core/scene/2dscene/{default-2d-scene-Horizontal.tsx → default-2d-scene/default-2d-scene-Horizontal.tsx} +1 -1
  22. package/src/core/scene/2dscene/{default-2d-scene-Landscape.tsx → default-2d-scene/default-2d-scene-Landscape.tsx} +1 -1
  23. package/src/core/scene/2dscene/{default-2d-scene.stories.tsx → default-2d-scene/default-2d-scene.stories.tsx} +1 -1
  24. package/src/core/scene/2dscene/{default-2d-scene.tsx → default-2d-scene/default-2d-scene.tsx} +4 -4
  25. package/src/core/scene/2dscene/index.ts +3 -3
  26. package/src/core/scene/3dscene/{default-3d-scene-Horizontal.stories.tsx → default-3d-scene/default-3d-scene-Horizontal.stories.tsx} +1 -1
  27. package/src/core/scene/3dscene/{default-3d-scene-Horizontal.tsx → default-3d-scene/default-3d-scene-Horizontal.tsx} +1 -1
  28. package/src/core/scene/3dscene/{default-3d-scene-Landscape.tsx → default-3d-scene/default-3d-scene-Landscape.tsx} +1 -1
  29. package/src/core/scene/3dscene/{default-3d-scene.stories.tsx → default-3d-scene/default-3d-scene.stories.tsx} +1 -1
  30. package/src/core/scene/3dscene/{default-3d-scene.tsx → default-3d-scene/default-3d-scene.tsx} +4 -4
  31. package/src/core/scene/3dscene/index.ts +3 -3
  32. package/src/core/scene/html-in-canvas/browser/default-html-in-canvas-browser-scene-Horizontal.stories.tsx +35 -0
  33. package/src/core/scene/html-in-canvas/browser/default-html-in-canvas-browser-scene-Landscape.stories.tsx +80 -0
  34. package/src/core/scene/html-in-canvas/dashboard/default-html-in-canvas-dashboard-scene-Horizontal.stories.tsx +35 -0
  35. package/src/core/scene/html-in-canvas/dashboard/default-html-in-canvas-dashboard-scene-Landscape.stories.tsx +35 -0
  36. package/src/core/scene/html-in-canvas/dashboard-shell/default-dashboard-shell-html-in-canvas-scene.stories.tsx +40 -0
  37. package/src/core/scene/html-in-canvas/dashboard-shell/default-dashboard-shell-html-in-canvas-scene.tsx +44 -0
  38. package/src/core/scene/html-in-canvas/dashboard-shell/index.ts +8 -0
  39. package/src/core/scene/html-in-canvas/docs/default-html-in-canvas-docs-scene-Horizontal.stories.tsx +35 -0
  40. package/src/core/scene/html-in-canvas/docs/default-html-in-canvas-docs-scene-Landscape.stories.tsx +35 -0
  41. package/src/core/scene/html-in-canvas/document/article/default-article-html-in-canvas-scene.stories.tsx +38 -0
  42. package/src/core/scene/html-in-canvas/document/article/default-article-html-in-canvas-scene.tsx +50 -0
  43. package/src/core/scene/html-in-canvas/document/article/index.ts +8 -0
  44. package/src/core/scene/html-in-canvas/document/default-document-html-in-canvas-scene.stories.tsx +40 -0
  45. package/src/core/scene/html-in-canvas/document/default-document-html-in-canvas-scene.tsx +50 -0
  46. package/src/core/scene/html-in-canvas/document/index.ts +31 -0
  47. package/src/core/scene/html-in-canvas/document/report/default-report-html-in-canvas-scene.stories.tsx +38 -0
  48. package/src/core/scene/html-in-canvas/document/report/default-report-html-in-canvas-scene.tsx +51 -0
  49. package/src/core/scene/html-in-canvas/document/report/index.ts +8 -0
  50. package/src/core/scene/html-in-canvas/document/whitepaper/default-whitepaper-html-in-canvas-scene.stories.tsx +41 -0
  51. package/src/core/scene/html-in-canvas/document/whitepaper/default-whitepaper-html-in-canvas-scene.tsx +50 -0
  52. package/src/core/scene/html-in-canvas/document/whitepaper/index.ts +8 -0
  53. package/src/core/scene/html-in-canvas/editor/default-html-in-canvas-editor-scene-Horizontal.stories.tsx +35 -0
  54. package/src/core/scene/html-in-canvas/editor/default-html-in-canvas-editor-scene-Landscape.stories.tsx +35 -0
  55. package/src/core/scene/html-in-canvas/full-bleed/default-full-bleed-html-in-canvas-scene.stories.tsx +40 -0
  56. package/src/core/scene/html-in-canvas/full-bleed/default-full-bleed-html-in-canvas-scene.tsx +38 -0
  57. package/src/core/scene/html-in-canvas/full-bleed/index.ts +8 -0
  58. package/src/core/scene/html-in-canvas/index.ts +6 -0
  59. package/src/core/scene/html-in-canvas/landing-page/default-landing-page-html-in-canvas-scene.stories.tsx +40 -0
  60. package/src/core/scene/html-in-canvas/landing-page/default-landing-page-html-in-canvas-scene.tsx +46 -0
  61. package/src/core/scene/html-in-canvas/landing-page/feature-grid/default-feature-grid-html-in-canvas-scene.stories.tsx +38 -0
  62. package/src/core/scene/html-in-canvas/landing-page/feature-grid/default-feature-grid-html-in-canvas-scene.tsx +41 -0
  63. package/src/core/scene/html-in-canvas/landing-page/feature-grid/index.ts +8 -0
  64. package/src/core/scene/html-in-canvas/landing-page/hero/default-hero-html-in-canvas-scene.stories.tsx +38 -0
  65. package/src/core/scene/html-in-canvas/landing-page/hero/default-hero-html-in-canvas-scene.tsx +46 -0
  66. package/src/core/scene/html-in-canvas/landing-page/hero/index.ts +8 -0
  67. package/src/core/scene/html-in-canvas/landing-page/index.ts +31 -0
  68. package/src/core/scene/html-in-canvas/landing-page/pricing/default-pricing-html-in-canvas-scene.stories.tsx +38 -0
  69. package/src/core/scene/html-in-canvas/landing-page/pricing/default-pricing-html-in-canvas-scene.tsx +42 -0
  70. package/src/core/scene/html-in-canvas/landing-page/pricing/index.ts +8 -0
  71. package/src/core/scene/html-in-canvas/media-wall/collage/default-collage-html-in-canvas-scene.stories.tsx +38 -0
  72. package/src/core/scene/html-in-canvas/media-wall/collage/default-collage-html-in-canvas-scene.tsx +42 -0
  73. package/src/core/scene/html-in-canvas/media-wall/collage/index.ts +8 -0
  74. package/src/core/scene/html-in-canvas/media-wall/default-media-wall-html-in-canvas-scene.stories.tsx +40 -0
  75. package/src/core/scene/html-in-canvas/media-wall/default-media-wall-html-in-canvas-scene.tsx +35 -0
  76. package/src/core/scene/html-in-canvas/media-wall/gallery/default-gallery-html-in-canvas-scene.stories.tsx +38 -0
  77. package/src/core/scene/html-in-canvas/media-wall/gallery/default-gallery-html-in-canvas-scene.tsx +43 -0
  78. package/src/core/scene/html-in-canvas/media-wall/gallery/index.ts +8 -0
  79. package/src/core/scene/html-in-canvas/media-wall/grid/default-grid-html-in-canvas-scene.stories.tsx +38 -0
  80. package/src/core/scene/html-in-canvas/media-wall/grid/default-grid-html-in-canvas-scene.tsx +35 -0
  81. package/src/core/scene/html-in-canvas/media-wall/grid/index.ts +8 -0
  82. package/src/core/scene/html-in-canvas/media-wall/index.ts +31 -0
  83. package/src/core/scene/html-in-canvas/shared/default-html-in-canvas-scene.tsx +1315 -0
  84. package/src/core/scene/index.ts +1 -0
  85. package/src/core/scene/photo/3dvr-photo/{default-3dvr-photo-scene.stories.tsx → default-3dvr-photo-scene/default-3dvr-photo-scene.stories.tsx} +2 -2
  86. package/src/core/scene/photo/3dvr-photo/{default-3dvr-photo-scene.tsx → default-3dvr-photo-scene/default-3dvr-photo-scene.tsx} +4 -4
  87. package/src/core/scene/photo/3dvr-photo/{default-3dvr-photo-scene-Horizontal.stories.tsx → default-3dvr-photo-scene-Horizontal/default-3dvr-photo-scene-Horizontal.stories.tsx} +1 -1
  88. package/src/core/scene/photo/3dvr-photo/{default-3dvr-photo-scene-Horizontal.tsx → default-3dvr-photo-scene-Horizontal/default-3dvr-photo-scene-Horizontal.tsx} +4 -4
  89. package/src/core/scene/photo/3dvr-photo/{default-3dvr-photo-scene-Landscape.tsx → default-3dvr-photo-scene-Landscape/default-3dvr-photo-scene-Landscape.tsx} +4 -4
  90. package/src/core/scene/photo/3dvr-photo/default-dolly-3dvr-photo-scene/default-dolly-3dvr-photo-scene.stories.tsx +38 -0
  91. package/src/core/scene/photo/3dvr-photo/default-dolly-3dvr-photo-scene/default-dolly-3dvr-photo-scene.tsx +36 -0
  92. package/src/core/scene/photo/3dvr-photo/default-orbit-3dvr-photo-scene/default-orbit-3dvr-photo-scene.stories.tsx +38 -0
  93. package/src/core/scene/photo/3dvr-photo/default-orbit-3dvr-photo-scene/default-orbit-3dvr-photo-scene.tsx +34 -0
  94. package/src/core/scene/photo/3dvr-photo/default-poster-3dvr-photo-scene/default-poster-3dvr-photo-scene.stories.tsx +38 -0
  95. package/src/core/scene/photo/3dvr-photo/default-poster-3dvr-photo-scene/default-poster-3dvr-photo-scene.tsx +39 -0
  96. package/src/core/scene/photo/3dvr-photo/index.ts +6 -3
  97. package/src/core/scene/photo/effects-photo/default-chrome-effects-photo-scene/default-chrome-effects-photo-scene.stories.tsx +24 -0
  98. package/src/core/scene/photo/effects-photo/default-chrome-effects-photo-scene/default-chrome-effects-photo-scene.tsx +39 -0
  99. package/src/core/scene/photo/effects-photo/default-cool-tone-effects-photo-scene/default-cool-tone-effects-photo-scene.stories.tsx +24 -0
  100. package/src/core/scene/photo/effects-photo/default-cool-tone-effects-photo-scene/default-cool-tone-effects-photo-scene.tsx +39 -0
  101. package/src/core/scene/photo/effects-photo/default-dream-effects-photo-scene/default-dream-effects-photo-scene.stories.tsx +23 -0
  102. package/src/core/scene/photo/effects-photo/default-dream-effects-photo-scene/default-dream-effects-photo-scene.tsx +37 -0
  103. package/src/core/scene/photo/effects-photo/default-duotone-effects-photo-scene/default-duotone-effects-photo-scene.stories.tsx +25 -0
  104. package/src/core/scene/photo/effects-photo/default-duotone-effects-photo-scene/default-duotone-effects-photo-scene.tsx +41 -0
  105. package/src/core/scene/photo/effects-photo/{default-effects-photo-scene.stories.tsx → default-effects-photo-scene/default-effects-photo-scene.stories.tsx} +2 -2
  106. package/src/core/scene/photo/effects-photo/default-effects-photo-scene/default-effects-photo-scene.tsx +639 -0
  107. package/src/core/scene/photo/effects-photo/{default-effects-photo-scene-Horizontal.stories.tsx → default-effects-photo-scene-Horizontal/default-effects-photo-scene-Horizontal.stories.tsx} +1 -1
  108. package/src/core/scene/photo/effects-photo/{default-effects-photo-scene-Horizontal.tsx → default-effects-photo-scene-Horizontal/default-effects-photo-scene-Horizontal.tsx} +4 -4
  109. package/src/core/scene/photo/effects-photo/{default-effects-photo-scene-Landscape.tsx → default-effects-photo-scene-Landscape/default-effects-photo-scene-Landscape.tsx} +4 -4
  110. package/src/core/scene/photo/effects-photo/default-glow-effects-photo-scene/default-glow-effects-photo-scene.stories.tsx +39 -0
  111. package/src/core/scene/photo/effects-photo/default-glow-effects-photo-scene/default-glow-effects-photo-scene.tsx +37 -0
  112. package/src/core/scene/photo/effects-photo/default-grain-effects-photo-scene/default-grain-effects-photo-scene.stories.tsx +39 -0
  113. package/src/core/scene/photo/effects-photo/default-grain-effects-photo-scene/default-grain-effects-photo-scene.tsx +37 -0
  114. package/src/core/scene/photo/effects-photo/default-halation-effects-photo-scene/default-halation-effects-photo-scene.stories.tsx +24 -0
  115. package/src/core/scene/photo/effects-photo/default-halation-effects-photo-scene/default-halation-effects-photo-scene.tsx +39 -0
  116. package/src/core/scene/photo/effects-photo/default-infrared-effects-photo-scene/default-infrared-effects-photo-scene.stories.tsx +24 -0
  117. package/src/core/scene/photo/effects-photo/default-infrared-effects-photo-scene/default-infrared-effects-photo-scene.tsx +39 -0
  118. package/src/core/scene/photo/effects-photo/default-light-leak-effects-photo-scene/default-light-leak-effects-photo-scene.stories.tsx +24 -0
  119. package/src/core/scene/photo/effects-photo/default-light-leak-effects-photo-scene/default-light-leak-effects-photo-scene.tsx +39 -0
  120. package/src/core/scene/photo/effects-photo/default-mono-effects-photo-scene/default-mono-effects-photo-scene.stories.tsx +23 -0
  121. package/src/core/scene/photo/effects-photo/default-mono-effects-photo-scene/default-mono-effects-photo-scene.tsx +37 -0
  122. package/src/core/scene/photo/effects-photo/default-noir-effects-photo-scene/default-noir-effects-photo-scene.stories.tsx +23 -0
  123. package/src/core/scene/photo/effects-photo/default-noir-effects-photo-scene/default-noir-effects-photo-scene.tsx +37 -0
  124. package/src/core/scene/photo/effects-photo/default-pastel-effects-photo-scene/default-pastel-effects-photo-scene.stories.tsx +24 -0
  125. package/src/core/scene/photo/effects-photo/default-pastel-effects-photo-scene/default-pastel-effects-photo-scene.tsx +39 -0
  126. package/src/core/scene/photo/effects-photo/default-retro-effects-photo-scene/default-retro-effects-photo-scene.stories.tsx +24 -0
  127. package/src/core/scene/photo/effects-photo/default-retro-effects-photo-scene/default-retro-effects-photo-scene.tsx +39 -0
  128. package/src/core/scene/photo/effects-photo/default-sepia-effects-photo-scene/default-sepia-effects-photo-scene.stories.tsx +23 -0
  129. package/src/core/scene/photo/effects-photo/default-sepia-effects-photo-scene/default-sepia-effects-photo-scene.tsx +37 -0
  130. package/src/core/scene/photo/effects-photo/default-soft-focus-effects-photo-scene/default-soft-focus-effects-photo-scene.stories.tsx +39 -0
  131. package/src/core/scene/photo/effects-photo/default-soft-focus-effects-photo-scene/default-soft-focus-effects-photo-scene.tsx +37 -0
  132. package/src/core/scene/photo/effects-photo/default-vibrant-effects-photo-scene/default-vibrant-effects-photo-scene.stories.tsx +23 -0
  133. package/src/core/scene/photo/effects-photo/default-vibrant-effects-photo-scene/default-vibrant-effects-photo-scene.tsx +37 -0
  134. package/src/core/scene/photo/effects-photo/default-vignette-effects-photo-scene/default-vignette-effects-photo-scene.stories.tsx +39 -0
  135. package/src/core/scene/photo/effects-photo/default-vignette-effects-photo-scene/default-vignette-effects-photo-scene.tsx +37 -0
  136. package/src/core/scene/photo/effects-photo/default-warmth-effects-photo-scene/default-warmth-effects-photo-scene.stories.tsx +40 -0
  137. package/src/core/scene/photo/effects-photo/default-warmth-effects-photo-scene/default-warmth-effects-photo-scene.tsx +39 -0
  138. package/src/core/scene/photo/effects-photo/index.ts +21 -3
  139. package/src/core/scene/photo/index.ts +25 -9
  140. package/src/core/scene/photo/{default-live-photo-scene-Horizontal.stories.tsx → live-photo/default-live-photo-scene-Horizontal.stories.tsx} +1 -1
  141. package/src/core/scene/photo/{default-live-photo-scene-Horizontal.tsx → live-photo/default-live-photo-scene-Horizontal.tsx} +1 -1
  142. package/src/core/scene/photo/{default-live-photo-scene-Landscape.tsx → live-photo/default-live-photo-scene-Landscape.tsx} +1 -1
  143. package/src/core/scene/photo/{default-live-photo-scene.stories.tsx → live-photo/default-live-photo-scene.stories.tsx} +1 -1
  144. package/src/core/scene/photo/{default-live-photo-scene.tsx → live-photo/default-live-photo-scene.tsx} +5 -3
  145. package/src/core/scene/photo/photo-storybook.test.tsx +2 -2
  146. package/src/core/scene/photo/{default-photo-strip-scene-Horizontal.tsx → photo-strip/default-photo-strip-scene-Horizontal.tsx} +1 -1
  147. package/src/core/scene/photo/{default-photo-strip-scene-Landscape.tsx → photo-strip/default-photo-strip-scene-Landscape.tsx} +1 -1
  148. package/src/core/scene/photo/{default-photo-strip-scene.stories.tsx → photo-strip/default-photo-strip-scene.stories.tsx} +1 -1
  149. package/src/core/scene/photo/{default-photo-strip-scene.tsx → photo-strip/default-photo-strip-scene.tsx} +2 -2
  150. package/src/core/scene/photo/{default-photo-wall-scene-Horizontal.tsx → photo-wall/default-photo-wall-scene-Horizontal.tsx} +1 -1
  151. package/src/core/scene/photo/{default-photo-wall-scene-Landscape.tsx → photo-wall/default-photo-wall-scene-Landscape.tsx} +1 -1
  152. package/src/core/scene/photo/{default-photo-wall-scene.stories.tsx → photo-wall/default-photo-wall-scene.stories.tsx} +1 -1
  153. package/src/core/scene/photo/{default-photo-wall-scene.tsx → photo-wall/default-photo-wall-scene.tsx} +2 -2
  154. package/src/core/scene/slide/{default-slide-scene-Horizontal.animated.stories.tsx → default-slide-scene/default-slide-scene-Horizontal.animated.stories.tsx} +1 -1
  155. package/src/core/scene/slide/{default-slide-scene-Horizontal.tsx → default-slide-scene/default-slide-scene-Horizontal.tsx} +1 -1
  156. package/src/core/scene/slide/{default-slide-scene-Landscape.tsx → default-slide-scene/default-slide-scene-Landscape.tsx} +1 -1
  157. package/src/core/scene/slide/{default-slide-scene.animated.stories.tsx → default-slide-scene/default-slide-scene.animated.stories.tsx} +1 -1
  158. package/src/core/scene/slide/{default-slide-scene.tsx → default-slide-scene/default-slide-scene.tsx} +5 -5
  159. package/src/core/scene/slide/index.ts +3 -3
  160. package/src/core/scene/video/{default-video-scene-Horizontal.stories.tsx → default-video-scene/default-video-scene-Horizontal.stories.tsx} +1 -1
  161. package/src/core/scene/video/{default-video-scene-Horizontal.tsx → default-video-scene/default-video-scene-Horizontal.tsx} +1 -1
  162. package/src/core/scene/video/{default-video-scene-Landscape.tsx → default-video-scene/default-video-scene-Landscape.tsx} +1 -1
  163. package/src/core/scene/video/{default-video-scene.stories.tsx → default-video-scene/default-video-scene.stories.tsx} +1 -1
  164. package/src/core/scene/video/{default-video-scene.tsx → default-video-scene/default-video-scene.tsx} +3 -3
  165. package/src/core/scene/video/{default-video-split-scene-Horizontal.tsx → default-video-split-scene/default-video-split-scene-Horizontal.tsx} +1 -1
  166. package/src/core/scene/video/{default-video-split-scene-Landscape.tsx → default-video-split-scene/default-video-split-scene-Landscape.tsx} +1 -1
  167. package/src/core/scene/video/{default-video-split-scene.stories.tsx → default-video-split-scene/default-video-split-scene.stories.tsx} +1 -1
  168. package/src/core/scene/video/{default-video-split-scene.tsx → default-video-split-scene/default-video-split-scene.tsx} +2 -2
  169. package/src/core/scene/video/{default-video-wall-scene-Horizontal.tsx → default-video-wall-scene/default-video-wall-scene-Horizontal.tsx} +1 -1
  170. package/src/core/scene/video/{default-video-wall-scene-Landscape.tsx → default-video-wall-scene/default-video-wall-scene-Landscape.tsx} +1 -1
  171. package/src/core/scene/video/{default-video-wall-scene.stories.tsx → default-video-wall-scene/default-video-wall-scene.stories.tsx} +1 -1
  172. package/src/core/scene/video/{default-video-wall-scene.tsx → default-video-wall-scene/default-video-wall-scene.tsx} +2 -2
  173. package/src/core/scene/video/index.ts +9 -9
  174. package/src/lint/index.ts +2 -0
  175. package/src/lint/scene-convention-lint.test.ts +86 -0
  176. package/src/lint/scene-convention-lint.ts +247 -0
  177. package/src/storybook/index.ts +19 -0
  178. package/src/storybook/remotion-preview.test.tsx +53 -0
  179. package/src/storybook/remotion-preview.tsx +85 -11
  180. package/src/core/scene/photo/effects-photo/default-effects-photo-scene.tsx +0 -352
  181. /package/src/core/scene/slide/{default-slide-scene-Horizontal.stories.tsx → default-slide-scene/default-slide-scene-Horizontal.stories.tsx} +0 -0
  182. /package/src/core/scene/slide/{default-slide-scene.stories.tsx → default-slide-scene/default-slide-scene.stories.tsx} +0 -0
@@ -3,10 +3,10 @@ import { z } from 'zod'
3
3
 
4
4
  import { VideoComponent, defineScenePluginMetadata } from '@vibecuting/component-project-helper'
5
5
 
6
- import { defineSceneComponent } from '../../../plugins/scene-component'
7
- import { SceneMotionStack } from '../../../base'
8
- import { useRemotionSceneRuntime } from '../../../base'
9
- import { useSceneRootProps } from '../../../runtime/use-scene-root-props'
6
+ import { defineSceneComponent } from '../../../../plugins/scene-component'
7
+ import { SceneMotionStack } from '../../../../base'
8
+ import { useRemotionSceneRuntime } from '../../../../base'
9
+ import { useSceneRootProps } from '../../../../runtime/use-scene-root-props'
10
10
 
11
11
  export interface DefaultSlideSceneProps {
12
12
  sceneId?: string
@@ -19,7 +19,7 @@ export const componentMetadata = defineScenePluginMetadata({
19
19
  resourceKind: 'scene',
20
20
  name: 'DefaultSlideScene',
21
21
  description: 'Default remotion slide scene',
22
- sourceFile: 'src/core/scene/slide/default-slide-scene.tsx',
22
+ sourceFile: 'src/core/scene/slide/default-slide-scene/default-slide-scene.tsx',
23
23
  pluginKey: 'core.slide',
24
24
  tags: ['scene', 'slide'],
25
25
  aspectRatio: '16:9',
@@ -1,5 +1,5 @@
1
1
  export * from './_shared'
2
- export { DefaultSlideScene } from './default-slide-scene'
2
+ export { DefaultSlideScene } from './default-slide-scene/default-slide-scene'
3
3
 
4
4
  export { DefaultAbstractBackgroundCoverSlide } from './abstract-background-cover'
5
5
  export { DefaultAgendaCardsSlide } from './agenda-cards'
@@ -85,8 +85,8 @@ export { DefaultTimelineCardsSlide } from './timeline-cards'
85
85
  export { DefaultTimelineSlide } from './timeline'
86
86
  export { DefaultTimelineSplitSlide } from './timeline-split'
87
87
  export { DefaultTitleWithImageSlide } from './title-with-image'
88
- export { DefaultSlideSceneLandscape, componentMetadata as DefaultSlideSceneLandscapeMetadata } from './default-slide-scene-Landscape'
89
- export { DefaultSlideSceneHorizontal, componentMetadata as DefaultSlideSceneHorizontalMetadata } from './default-slide-scene-Horizontal'
88
+ export { DefaultSlideSceneLandscape, componentMetadata as DefaultSlideSceneLandscapeMetadata } from './default-slide-scene/default-slide-scene-Landscape'
89
+ export { DefaultSlideSceneHorizontal, componentMetadata as DefaultSlideSceneHorizontalMetadata } from './default-slide-scene/default-slide-scene-Horizontal'
90
90
 
91
91
  const slideSceneMetadata = { sceneFamily: 'slide' as const }
92
92
 
@@ -4,7 +4,7 @@ const photoUrl = 'https://heyaai-statics-1342239856.cos.ap-guangzhou.myqcloud.co
4
4
  const frame = <img src={photoUrl} alt="Video preview" />
5
5
 
6
6
  const meta = {
7
- title: 'core/scene/video/DefaultVideoSceneHorizontal',
7
+ title: 'core/scene/video/default-video-scene/DefaultVideoSceneHorizontal',
8
8
  component: DefaultVideoSceneHorizontal,
9
9
  args: {
10
10
  title: 'Launch Cut',
@@ -8,7 +8,7 @@ export const componentMetadata: ScenePluginMetadata = {
8
8
  ...baseComponentMetadata,
9
9
  name: 'DefaultVideoSceneHorizontal',
10
10
  description: 'Video preview scene (Portrait 9:16 variant)',
11
- sourceFile: 'src/core/scene/video/default-video-scene-Horizontal.tsx',
11
+ sourceFile: 'src/core/scene/video/default-video-scene/default-video-scene-Horizontal.tsx',
12
12
  aspectRatio: '9:16',
13
13
  tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
14
14
  }
@@ -8,7 +8,7 @@ export const componentMetadata: ScenePluginMetadata = {
8
8
  ...baseComponentMetadata,
9
9
  name: 'DefaultVideoSceneLandscape',
10
10
  description: 'Video preview scene (Landscape 16:9 variant)',
11
- sourceFile: 'src/core/scene/video/default-video-scene-Landscape.tsx',
11
+ sourceFile: 'src/core/scene/video/default-video-scene/default-video-scene-Landscape.tsx',
12
12
  aspectRatio: '16:9',
13
13
  tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
14
14
  }
@@ -4,7 +4,7 @@ const photoUrl = 'https://heyaai-statics-1342239856.cos.ap-guangzhou.myqcloud.co
4
4
  const frame = <img src={photoUrl} alt="Video preview" />
5
5
 
6
6
  const meta = {
7
- title: 'core/scene/video/DefaultVideoSceneLandscape',
7
+ title: 'core/scene/video/default-video-scene/DefaultVideoSceneLandscape',
8
8
  component: DefaultVideoSceneLandscape,
9
9
  args: {
10
10
  title: 'Launch Cut',
@@ -6,8 +6,8 @@ import { VideoComponent, defineScenePluginMetadata } from '@vibecuting/component
6
6
  import { cloneElement, isValidElement } from 'react'
7
7
  import { spring, useCurrentFrame, useVideoConfig } from 'remotion'
8
8
 
9
- import { BaseRemotionScene, SceneMotionLayer, type BaseRemotionSceneProps, useRemotionSceneRuntime, useSceneConfig } from '../../../base'
10
- import { defineSceneComponent } from '../../../plugins/scene-component'
9
+ import { BaseRemotionScene, SceneMotionLayer, type BaseRemotionSceneProps, useRemotionSceneRuntime, useSceneConfig } from '../../../../base'
10
+ import { defineSceneComponent } from '../../../../plugins/scene-component'
11
11
 
12
12
  export interface DefaultVideoSceneProps extends BaseRemotionSceneProps {
13
13
  sceneId?: string
@@ -29,7 +29,7 @@ export const componentMetadata = defineScenePluginMetadata({
29
29
  resourceKind: 'scene',
30
30
  name: 'DefaultVideoScene',
31
31
  description: 'Video-first scene with poster-led motion and editorial framing',
32
- sourceFile: 'src/core/scene/video/default-video-scene.tsx',
32
+ sourceFile: 'src/core/scene/video/default-video-scene/default-video-scene.tsx',
33
33
  pluginKey: 'core.video.preview',
34
34
  tags: ['scene', 'video', 'preview', 'player'],
35
35
  aspectRatio: '16:9',
@@ -8,7 +8,7 @@ export const componentMetadata: ScenePluginMetadata = {
8
8
  ...baseComponentMetadata,
9
9
  name: 'DefaultVideoSplitSceneHorizontal',
10
10
  description: 'Video split preview scene (Portrait 9:16 variant)',
11
- sourceFile: 'src/core/scene/video/default-video-split-scene-Horizontal.tsx',
11
+ sourceFile: 'src/core/scene/video/default-video-split-scene/default-video-split-scene-Horizontal.tsx',
12
12
  aspectRatio: '9:16',
13
13
  tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
14
14
  }
@@ -8,7 +8,7 @@ export const componentMetadata: ScenePluginMetadata = {
8
8
  ...baseComponentMetadata,
9
9
  name: 'DefaultVideoSplitSceneLandscape',
10
10
  description: 'Video split preview scene (Landscape 16:9 variant)',
11
- sourceFile: 'src/core/scene/video/default-video-split-scene-Landscape.tsx',
11
+ sourceFile: 'src/core/scene/video/default-video-split-scene/default-video-split-scene-Landscape.tsx',
12
12
  aspectRatio: '16:9',
13
13
  tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
14
14
  }
@@ -22,7 +22,7 @@ const args = {
22
22
  }
23
23
 
24
24
  const meta = {
25
- title: 'core/scene/video/DefaultVideoSplitSceneLandscape',
25
+ title: 'core/scene/video/default-video-split-scene/DefaultVideoSplitSceneLandscape',
26
26
  component: DefaultVideoSplitSceneLandscape,
27
27
  args,
28
28
  }
@@ -2,13 +2,13 @@ import type { ComponentProps } from 'react'
2
2
 
3
3
  import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
4
 
5
- import { DefaultVideoScene, componentMetadata as baseComponentMetadata } from './default-video-scene'
5
+ import { DefaultVideoScene, componentMetadata as baseComponentMetadata } from '../default-video-scene/default-video-scene'
6
6
 
7
7
  export const componentMetadata: ScenePluginMetadata = {
8
8
  ...baseComponentMetadata,
9
9
  name: 'DefaultVideoSplitScene',
10
10
  description: 'Video split preview scene',
11
- sourceFile: 'src/core/scene/video/default-video-split-scene.tsx',
11
+ sourceFile: 'src/core/scene/video/default-video-split-scene/default-video-split-scene.tsx',
12
12
  tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'split'])),
13
13
  }
14
14
 
@@ -8,7 +8,7 @@ export const componentMetadata: ScenePluginMetadata = {
8
8
  ...baseComponentMetadata,
9
9
  name: 'DefaultVideoWallSceneHorizontal',
10
10
  description: 'Video wall preview scene (Portrait 9:16 variant)',
11
- sourceFile: 'src/core/scene/video/default-video-wall-scene-Horizontal.tsx',
11
+ sourceFile: 'src/core/scene/video/default-video-wall-scene/default-video-wall-scene-Horizontal.tsx',
12
12
  aspectRatio: '9:16',
13
13
  tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
14
14
  }
@@ -8,7 +8,7 @@ export const componentMetadata: ScenePluginMetadata = {
8
8
  ...baseComponentMetadata,
9
9
  name: 'DefaultVideoWallSceneLandscape',
10
10
  description: 'Video wall preview scene (Landscape 16:9 variant)',
11
- sourceFile: 'src/core/scene/video/default-video-wall-scene-Landscape.tsx',
11
+ sourceFile: 'src/core/scene/video/default-video-wall-scene/default-video-wall-scene-Landscape.tsx',
12
12
  aspectRatio: '16:9',
13
13
  tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
14
14
  }
@@ -21,7 +21,7 @@ const args = {
21
21
  }
22
22
 
23
23
  const meta = {
24
- title: 'core/scene/video/DefaultVideoWallSceneLandscape',
24
+ title: 'core/scene/video/default-video-wall-scene/DefaultVideoWallSceneLandscape',
25
25
  component: DefaultVideoWallSceneLandscape,
26
26
  args,
27
27
  }
@@ -2,13 +2,13 @@ import type { ComponentProps } from 'react'
2
2
 
3
3
  import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
4
 
5
- import { DefaultVideoScene, componentMetadata as baseComponentMetadata } from './default-video-scene'
5
+ import { DefaultVideoScene, componentMetadata as baseComponentMetadata } from '../default-video-scene/default-video-scene'
6
6
 
7
7
  export const componentMetadata: ScenePluginMetadata = {
8
8
  ...baseComponentMetadata,
9
9
  name: 'DefaultVideoWallScene',
10
10
  description: 'Video wall preview scene',
11
- sourceFile: 'src/core/scene/video/default-video-wall-scene.tsx',
11
+ sourceFile: 'src/core/scene/video/default-video-wall-scene/default-video-wall-scene.tsx',
12
12
  tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'wall', 'multi'])),
13
13
  }
14
14
 
@@ -1,9 +1,9 @@
1
- export { DefaultVideoScene, componentMetadata as DefaultVideoSceneMetadata } from './default-video-scene'
2
- export { DefaultVideoSceneLandscape, componentMetadata as DefaultVideoSceneLandscapeMetadata } from './default-video-scene-Landscape'
3
- export { DefaultVideoSceneHorizontal, componentMetadata as DefaultVideoSceneHorizontalMetadata } from './default-video-scene-Horizontal'
4
- export { DefaultVideoSplitScene, componentMetadata as DefaultVideoSplitSceneMetadata } from './default-video-split-scene'
5
- export { DefaultVideoSplitSceneLandscape, componentMetadata as DefaultVideoSplitSceneLandscapeMetadata } from './default-video-split-scene-Landscape'
6
- export { DefaultVideoSplitSceneHorizontal, componentMetadata as DefaultVideoSplitSceneHorizontalMetadata } from './default-video-split-scene-Horizontal'
7
- export { DefaultVideoWallScene, componentMetadata as DefaultVideoWallSceneMetadata } from './default-video-wall-scene'
8
- export { DefaultVideoWallSceneLandscape, componentMetadata as DefaultVideoWallSceneLandscapeMetadata } from './default-video-wall-scene-Landscape'
9
- export { DefaultVideoWallSceneHorizontal, componentMetadata as DefaultVideoWallSceneHorizontalMetadata } from './default-video-wall-scene-Horizontal'
1
+ export { DefaultVideoScene, componentMetadata as DefaultVideoSceneMetadata } from './default-video-scene/default-video-scene'
2
+ export { DefaultVideoSceneLandscape, componentMetadata as DefaultVideoSceneLandscapeMetadata } from './default-video-scene/default-video-scene-Landscape'
3
+ export { DefaultVideoSceneHorizontal, componentMetadata as DefaultVideoSceneHorizontalMetadata } from './default-video-scene/default-video-scene-Horizontal'
4
+ export { DefaultVideoSplitScene, componentMetadata as DefaultVideoSplitSceneMetadata } from './default-video-split-scene/default-video-split-scene'
5
+ export { DefaultVideoSplitSceneLandscape, componentMetadata as DefaultVideoSplitSceneLandscapeMetadata } from './default-video-split-scene/default-video-split-scene-Landscape'
6
+ export { DefaultVideoSplitSceneHorizontal, componentMetadata as DefaultVideoSplitSceneHorizontalMetadata } from './default-video-split-scene/default-video-split-scene-Horizontal'
7
+ export { DefaultVideoWallScene, componentMetadata as DefaultVideoWallSceneMetadata } from './default-video-wall-scene/default-video-wall-scene'
8
+ export { DefaultVideoWallSceneLandscape, componentMetadata as DefaultVideoWallSceneLandscapeMetadata } from './default-video-wall-scene/default-video-wall-scene-Landscape'
9
+ export { DefaultVideoWallSceneHorizontal, componentMetadata as DefaultVideoWallSceneHorizontalMetadata } from './default-video-wall-scene/default-video-wall-scene-Horizontal'
@@ -0,0 +1,2 @@
1
+ export { lintSceneConvention } from './scene-convention-lint'
2
+ export type { SceneConventionLintOptions, SceneConventionLintResult } from './scene-convention-lint'
@@ -0,0 +1,86 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+
5
+ import { describe, expect, it } from 'vitest'
6
+
7
+ import { lintSceneConvention } from './scene-convention-lint'
8
+
9
+ function writeSceneFixture(rootDir: string) {
10
+ const folder = path.join(rootDir, 'src/components/default-component-scene')
11
+ const sharedFolder = path.join(rootDir, 'src/components/_shared')
12
+ fs.mkdirSync(folder, { recursive: true })
13
+ fs.mkdirSync(sharedFolder, { recursive: true })
14
+
15
+ fs.writeFileSync(
16
+ path.join(folder, 'default-component-scene.tsx'),
17
+ [
18
+ "import { z } from 'zod'",
19
+ "export const componentMetadata = { sourceFile: 'src/components/default-component-scene/default-component-scene.tsx' }",
20
+ "export const DefaultComponentScene = defineSceneComponent({ propsSchema: z.object({ title: z.string() }), component: function DefaultComponentScene() { useRemotionSceneRuntime(); return <SceneMotionLayer /> } })",
21
+ '',
22
+ ].join('\n'),
23
+ )
24
+ fs.writeFileSync(
25
+ path.join(folder, 'default-component-scene-Landscape.tsx'),
26
+ "export const componentMetadata = { sourceFile: 'src/components/default-component-scene/default-component-scene-Landscape.tsx', aspectRatio: '16:9' }\n",
27
+ )
28
+ fs.writeFileSync(
29
+ path.join(folder, 'default-component-scene-Horizontal.tsx'),
30
+ "export const componentMetadata = { sourceFile: 'src/components/default-component-scene/default-component-scene-Horizontal.tsx', aspectRatio: '9:16' }\n",
31
+ )
32
+ fs.writeFileSync(
33
+ path.join(folder, 'default-component-scene.stories.tsx'),
34
+ "const meta = { title: 'components/default-component-scene/DefaultComponentSceneLandscape' }\nexport default meta\nexport const Default = { parameters: { scenePreviewAspectRatio: '16:9' } }\n",
35
+ )
36
+ fs.writeFileSync(
37
+ path.join(folder, 'default-component-scene.animated.stories.tsx'),
38
+ "const meta = { title: 'components/default-component-scene/DefaultComponentSceneLandscape' }\nexport default meta\nexport const Animated = { parameters: { scenePreviewAspectRatio: '16:9', scenePreviewMode: 'animation' } }\n",
39
+ )
40
+ fs.writeFileSync(
41
+ path.join(folder, 'default-component-scene-Horizontal.stories.tsx'),
42
+ "const meta = { title: 'components/default-component-scene/DefaultComponentSceneHorizontal' }\nexport default meta\nexport const Default = { parameters: { scenePreviewAspectRatio: '9:16' } }\n",
43
+ )
44
+ fs.writeFileSync(
45
+ path.join(folder, 'default-component-scene-Horizontal.animated.stories.tsx'),
46
+ "const meta = { title: 'components/default-component-scene/DefaultComponentSceneHorizontal' }\nexport default meta\nexport const Animated = { parameters: { scenePreviewAspectRatio: '9:16', scenePreviewMode: 'animation' } }\n",
47
+ )
48
+ fs.writeFileSync(path.join(folder, 'default-component-scene.test.tsx'), "import test from 'node:test'\ntest('ok', () => {})\n")
49
+ fs.writeFileSync(path.join(folder, 'index.ts'), "export * from './default-component-scene'\n")
50
+ fs.writeFileSync(path.join(sharedFolder, 'scene-frame.tsx'), 'export function SceneFrame() { return null }\n')
51
+ }
52
+
53
+ describe('scene convention lint', () => {
54
+ it('passes on the expected folder structure', () => {
55
+ const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'scene-convention-lint-'))
56
+ writeSceneFixture(rootDir)
57
+
58
+ const result = lintSceneConvention({
59
+ rootDir,
60
+ sourceRoot: 'src/components',
61
+ titleRoot: 'components',
62
+ sharedFolderName: '_shared',
63
+ })
64
+
65
+ expect(result.errors).toEqual([])
66
+ expect(result.checkedSceneFolders).toContain('src/components/default-component-scene')
67
+ })
68
+
69
+ it('reports missing animation mode', () => {
70
+ const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'scene-convention-lint-missing-animation-'))
71
+ writeSceneFixture(rootDir)
72
+ fs.writeFileSync(
73
+ path.join(rootDir, 'src/components/default-component-scene/default-component-scene.animated.stories.tsx'),
74
+ "const meta = { title: 'components/default-component-scene/DefaultComponentSceneLandscape' }\nexport default meta\nexport const Animated = { parameters: { scenePreviewAspectRatio: '16:9' } }\n",
75
+ )
76
+
77
+ const result = lintSceneConvention({
78
+ rootDir,
79
+ sourceRoot: 'src/components',
80
+ titleRoot: 'components',
81
+ sharedFolderName: '_shared',
82
+ })
83
+
84
+ expect(result.errors.some((error) => error.includes("scenePreviewMode: 'animation'"))).toBe(true)
85
+ })
86
+ })
@@ -0,0 +1,247 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ export type SceneConventionLintOptions = {
5
+ rootDir: string
6
+ sourceRoot: string
7
+ titleRoot: string
8
+ checkedFolderPrefix?: string
9
+ sharedFolderName?: string
10
+ fix?: boolean
11
+ }
12
+
13
+ export type SceneConventionLintResult = {
14
+ checkedSceneFolders: string[]
15
+ checkedStoryFiles: string[]
16
+ errors: string[]
17
+ }
18
+
19
+ const toPosix = (value: string) => value.split(path.sep).join('/')
20
+
21
+ function toRootRelative(rootDir: string, file: string) {
22
+ if (path.isAbsolute(file)) {
23
+ return toPosix(path.relative(rootDir, file))
24
+ }
25
+
26
+ return toPosix(file).replace(/^\.\//, '')
27
+ }
28
+
29
+ function resolveRootPath(rootDir: string, file: string) {
30
+ return path.resolve(rootDir, toRootRelative(rootDir, file))
31
+ }
32
+
33
+ function readText(rootDir: string, file: string) {
34
+ return fs.readFileSync(resolveRootPath(rootDir, file), 'utf8')
35
+ }
36
+
37
+ function writeText(rootDir: string, file: string, text: string) {
38
+ fs.writeFileSync(resolveRootPath(rootDir, file), text)
39
+ }
40
+
41
+ function readStoryTitle(text: string) {
42
+ const titleMatch = text.match(/const\s+meta\s*=\s*{[\s\S]*?title:\s*['"`]([^'"`]+)['"`]/m)
43
+ return titleMatch?.[1] ?? ''
44
+ }
45
+
46
+ function replaceStoryTitlePrefix(text: string, expectedPrefix: string) {
47
+ return text.replace(
48
+ /(const\s+meta\s*=\s*{[\s\S]*?title:\s*['"`])([^'"`]+)(['"`])/m,
49
+ (_match, before: string, currentTitle: string, after: string) => {
50
+ const titleLabel = currentTitle.includes('/') ? currentTitle.split('/').filter(Boolean).at(-1) : currentTitle
51
+ return `${before}${expectedPrefix}${titleLabel}${after}`
52
+ },
53
+ )
54
+ }
55
+
56
+ function collectFiles(rootDir: string, dir: string, suffix: string) {
57
+ const absoluteDir = resolveRootPath(rootDir, dir)
58
+ const results: string[] = []
59
+ const stack = [absoluteDir]
60
+
61
+ while (stack.length > 0) {
62
+ const current = stack.pop()
63
+ if (!current || !fs.existsSync(current)) {
64
+ continue
65
+ }
66
+
67
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
68
+ const fullPath = path.join(current, entry.name)
69
+ if (entry.isDirectory()) {
70
+ stack.push(fullPath)
71
+ continue
72
+ }
73
+ if (entry.isFile() && entry.name.endsWith(suffix)) {
74
+ results.push(toRootRelative(rootDir, fullPath))
75
+ }
76
+ }
77
+ }
78
+
79
+ return results.sort()
80
+ }
81
+
82
+ function collectSceneFolders(options: SceneConventionLintOptions) {
83
+ const sourceRootPath = resolveRootPath(options.rootDir, options.sourceRoot)
84
+ const sharedFolderName = options.sharedFolderName ?? '_shared'
85
+
86
+ if (!fs.existsSync(sourceRootPath)) {
87
+ return []
88
+ }
89
+
90
+ return fs
91
+ .readdirSync(sourceRootPath, { withFileTypes: true })
92
+ .filter((entry) => entry.isDirectory())
93
+ .filter((entry) => entry.name !== sharedFolderName)
94
+ .filter((entry) => !options.checkedFolderPrefix || entry.name.startsWith(options.checkedFolderPrefix))
95
+ .map((entry) => toRootRelative(options.rootDir, path.join(sourceRootPath, entry.name)))
96
+ .sort()
97
+ }
98
+
99
+ function lintSceneFolder(options: SceneConventionLintOptions, folder: string) {
100
+ const slug = path.posix.basename(folder)
101
+ const errors: string[] = []
102
+ const requiredFiles = [
103
+ `${slug}.tsx`,
104
+ `${slug}-Landscape.tsx`,
105
+ `${slug}-Horizontal.tsx`,
106
+ `${slug}.stories.tsx`,
107
+ `${slug}.animated.stories.tsx`,
108
+ `${slug}-Horizontal.stories.tsx`,
109
+ `${slug}-Horizontal.animated.stories.tsx`,
110
+ `${slug}.test.tsx`,
111
+ 'index.ts',
112
+ ]
113
+
114
+ for (const fileName of requiredFiles) {
115
+ const requiredPath = `${folder}/${fileName}`
116
+ if (!fs.existsSync(resolveRootPath(options.rootDir, requiredPath))) {
117
+ errors.push(`${folder}: missing ${fileName}`)
118
+ }
119
+ }
120
+
121
+ const filesToCheck = [
122
+ `${folder}/${slug}.tsx`,
123
+ `${folder}/${slug}-Landscape.tsx`,
124
+ `${folder}/${slug}-Horizontal.tsx`,
125
+ ]
126
+
127
+ for (const file of filesToCheck) {
128
+ if (!fs.existsSync(resolveRootPath(options.rootDir, file))) {
129
+ continue
130
+ }
131
+
132
+ const text = readText(options.rootDir, file)
133
+ const sourceFileMatch = text.match(/sourceFile:\s*['"`]([^'"`]+)['"`]/)
134
+ if (!sourceFileMatch) {
135
+ errors.push(`${file}: missing componentMetadata.sourceFile`)
136
+ } else if (sourceFileMatch[1] !== file) {
137
+ errors.push(`${file}: sourceFile must be "${file}", got "${sourceFileMatch[1]}"`)
138
+ }
139
+ }
140
+
141
+ const baseFile = `${folder}/${slug}.tsx`
142
+ if (fs.existsSync(resolveRootPath(options.rootDir, baseFile))) {
143
+ const text = readText(options.rootDir, baseFile)
144
+ if (!/defineSceneComponent\s*\(/.test(text)) {
145
+ errors.push(`${baseFile}: base scene must use defineSceneComponent(...)`)
146
+ }
147
+ if (!/propsSchema:\s*z\.object\s*\(/.test(text)) {
148
+ errors.push(`${baseFile}: base scene must colocate propsSchema with z.object(...)`)
149
+ }
150
+ if (!/useRemotionSceneRuntime\s*\(/.test(text)) {
151
+ errors.push(`${baseFile}: base scene must use useRemotionSceneRuntime()`)
152
+ }
153
+ if (!/SceneMotionLayer|SceneMotionStack|useSceneMotion/.test(text)) {
154
+ errors.push(`${baseFile}: scene animation must use SceneMotionLayer, SceneMotionStack, or useSceneMotion`)
155
+ }
156
+ }
157
+
158
+ const landscapeFile = `${folder}/${slug}-Landscape.tsx`
159
+ if (
160
+ fs.existsSync(resolveRootPath(options.rootDir, landscapeFile)) &&
161
+ !/aspectRatio:\s*['"]16:9['"]/.test(readText(options.rootDir, landscapeFile))
162
+ ) {
163
+ errors.push(`${landscapeFile}: Landscape variant must set aspectRatio: '16:9'`)
164
+ }
165
+
166
+ const horizontalFile = `${folder}/${slug}-Horizontal.tsx`
167
+ if (
168
+ fs.existsSync(resolveRootPath(options.rootDir, horizontalFile)) &&
169
+ !/aspectRatio:\s*['"]9:16['"]/.test(readText(options.rootDir, horizontalFile))
170
+ ) {
171
+ errors.push(`${horizontalFile}: Horizontal variant must set aspectRatio: '9:16'`)
172
+ }
173
+
174
+ return errors
175
+ }
176
+
177
+ function lintStoryFile(options: SceneConventionLintOptions, storyFile: string) {
178
+ const normalized = toRootRelative(options.rootDir, storyFile)
179
+ const text = readText(options.rootDir, normalized)
180
+ const errors: string[] = []
181
+ const storyDir = path.posix.dirname(normalized.slice(`${options.sourceRoot}/`.length))
182
+ const expectedPrefix = `${options.titleRoot}/${storyDir}/`
183
+ const title = readStoryTitle(text)
184
+ const isAnimatedStory = normalized.endsWith('.animated.stories.tsx')
185
+ const isHorizontalStory = /-Horizontal(\.animated)?\.stories\.tsx$/.test(normalized)
186
+ const isLandscapeStory = !isHorizontalStory
187
+
188
+ if (!title.startsWith(expectedPrefix)) {
189
+ if (options.fix) {
190
+ writeText(options.rootDir, normalized, replaceStoryTitlePrefix(text, expectedPrefix))
191
+ } else {
192
+ errors.push(`${normalized}: story title must start with "${expectedPrefix}"`)
193
+ }
194
+ }
195
+
196
+ if (isAnimatedStory && !/scenePreviewMode:\s*['"]animation['"]/.test(text)) {
197
+ errors.push(`${normalized}: animated stories must set scenePreviewMode: 'animation'`)
198
+ }
199
+
200
+ if (isLandscapeStory && !/scenePreviewAspectRatio:\s*['"]16:9['"]/.test(text)) {
201
+ errors.push(`${normalized}: landscape stories must set scenePreviewAspectRatio: '16:9'`)
202
+ }
203
+
204
+ if (isHorizontalStory && !/scenePreviewAspectRatio:\s*['"]9:16['"]/.test(text)) {
205
+ errors.push(`${normalized}: horizontal stories must set scenePreviewAspectRatio: '9:16'`)
206
+ }
207
+
208
+ return errors
209
+ }
210
+
211
+ function lintSharedFolder(options: SceneConventionLintOptions) {
212
+ const sharedFolderName = options.sharedFolderName ?? '_shared'
213
+ const sharedRoot = `${options.sourceRoot}/${sharedFolderName}`
214
+ const sharedRootPath = resolveRootPath(options.rootDir, sharedRoot)
215
+ const errors: string[] = []
216
+
217
+ if (!fs.existsSync(sharedRootPath)) {
218
+ return errors
219
+ }
220
+
221
+ for (const file of collectFiles(options.rootDir, sharedRoot, '.tsx')) {
222
+ const text = readText(options.rootDir, file)
223
+ if (/componentMetadata|defineComponentProjectComponentMetadata|defineScenePluginMetadata/.test(text)) {
224
+ errors.push(`${file}: shared files must not declare publishable component metadata`)
225
+ }
226
+ }
227
+
228
+ return errors
229
+ }
230
+
231
+ export function lintSceneConvention(options: SceneConventionLintOptions): SceneConventionLintResult {
232
+ const sceneFolders = collectSceneFolders(options)
233
+ const storyFiles = collectFiles(options.rootDir, options.sourceRoot, '.stories.tsx').filter(
234
+ (file) => !file.includes(`/${options.sharedFolderName ?? '_shared'}/`),
235
+ )
236
+ const errors = [
237
+ ...sceneFolders.flatMap((folder) => lintSceneFolder(options, folder)),
238
+ ...storyFiles.flatMap((file) => lintStoryFile(options, file)),
239
+ ...lintSharedFolder(options),
240
+ ]
241
+
242
+ return {
243
+ checkedSceneFolders: sceneFolders,
244
+ checkedStoryFiles: storyFiles,
245
+ errors,
246
+ }
247
+ }
@@ -0,0 +1,19 @@
1
+ export { SceneViewport } from './SceneViewport'
2
+ export { createSceneStoryVariants } from './scene-story'
3
+ export {
4
+ AbsoluteFill,
5
+ Audio,
6
+ Easing,
7
+ HtmlInCanvas,
8
+ Img,
9
+ RemotionPreviewProvider,
10
+ Sequence,
11
+ interpolate,
12
+ isHtmlInCanvasSupported,
13
+ random,
14
+ spring,
15
+ staticFile,
16
+ useCurrentFrame,
17
+ useVideoConfig,
18
+ } from './remotion-preview'
19
+ export type { RemotionPreviewValue } from './remotion-preview'
@@ -0,0 +1,53 @@
1
+ import { render } from '@testing-library/react'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import {
5
+ AbsoluteFill,
6
+ Easing,
7
+ Img,
8
+ RemotionPreviewProvider,
9
+ Sequence,
10
+ interpolate,
11
+ random,
12
+ spring,
13
+ staticFile,
14
+ useCurrentFrame,
15
+ useVideoConfig,
16
+ } from './remotion-preview'
17
+
18
+ describe('storybook remotion preview shim', () => {
19
+ it('exports spring, interpolate and helpers', () => {
20
+ expect(spring({ frame: 30, fps: 30 })).toBeGreaterThan(0)
21
+ expect(interpolate(0.5, [0, 1], [10, 20])).toBe(15)
22
+ expect(random('seed')).toBeGreaterThanOrEqual(0)
23
+ expect(staticFile('/foo.png')).toBe('/foo.png')
24
+ expect(Easing.linear(0.5)).toBe(0.5)
25
+ expect(AbsoluteFill).toBeTypeOf('function')
26
+ expect(Sequence).toBeTypeOf('function')
27
+ expect(Img).toBeTypeOf('function')
28
+ })
29
+
30
+ it('provides frame and video config through the provider', () => {
31
+ const Probe = () => {
32
+ const frame = useCurrentFrame()
33
+ const config = useVideoConfig()
34
+ return (
35
+ <div>
36
+ frame:{frame}
37
+ width:{config.width}
38
+ height:{config.height}
39
+ </div>
40
+ )
41
+ }
42
+
43
+ const { getByText } = render(
44
+ <RemotionPreviewProvider value={{ frame: 12, fps: 30, width: 720, height: 1280, durationInFrames: 150 }}>
45
+ <Probe />
46
+ </RemotionPreviewProvider>,
47
+ )
48
+
49
+ expect(getByText(/frame:12/)).toBeTruthy()
50
+ expect(getByText(/width:720/)).toBeTruthy()
51
+ expect(getByText(/height:1280/)).toBeTruthy()
52
+ })
53
+ })