@vibecuting/video-project-core 0.1.27 → 0.1.29

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 (54) hide show
  1. package/package.json +2 -2
  2. package/src/base/BaseRemotionScene.tsx +4 -3
  3. package/src/core/index.test.ts +40 -0
  4. package/src/core/scene/2dscene/default-2d-scene-Horizontal.stories.tsx +1 -1
  5. package/src/core/scene/2dscene/default-2d-scene.stories.tsx +1 -1
  6. package/src/core/scene/3dscene/default-3d-scene-Horizontal.stories.tsx +1 -1
  7. package/src/core/scene/3dscene/default-3d-scene.stories.tsx +1 -1
  8. package/src/core/scene/index.ts +3 -0
  9. package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene-Horizontal.stories.tsx +46 -0
  10. package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene-Horizontal.tsx +20 -0
  11. package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene-Landscape.tsx +20 -0
  12. package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene.stories.tsx +46 -0
  13. package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene.tsx +255 -0
  14. package/src/core/scene/photo/3dvr-photo/index.ts +3 -0
  15. package/src/core/scene/photo/default-live-photo-scene-Horizontal.stories.tsx +33 -0
  16. package/src/core/scene/photo/default-live-photo-scene-Horizontal.tsx +20 -0
  17. package/src/core/scene/photo/default-live-photo-scene-Landscape.tsx +20 -0
  18. package/src/core/scene/photo/default-live-photo-scene.stories.tsx +33 -0
  19. package/src/core/scene/photo/default-live-photo-scene.tsx +432 -0
  20. package/src/core/scene/photo/default-photo-strip-scene-Horizontal.tsx +20 -0
  21. package/src/core/scene/photo/default-photo-strip-scene-Landscape.tsx +20 -0
  22. package/src/core/scene/photo/default-photo-strip-scene.stories.tsx +38 -0
  23. package/src/core/scene/photo/default-photo-strip-scene.tsx +19 -0
  24. package/src/core/scene/photo/default-photo-wall-scene-Horizontal.tsx +20 -0
  25. package/src/core/scene/photo/default-photo-wall-scene-Landscape.tsx +20 -0
  26. package/src/core/scene/photo/default-photo-wall-scene.stories.tsx +38 -0
  27. package/src/core/scene/photo/default-photo-wall-scene.tsx +19 -0
  28. package/src/core/scene/photo/effects-photo/default-effects-photo-scene-Horizontal.stories.tsx +44 -0
  29. package/src/core/scene/photo/effects-photo/default-effects-photo-scene-Horizontal.tsx +20 -0
  30. package/src/core/scene/photo/effects-photo/default-effects-photo-scene-Landscape.tsx +20 -0
  31. package/src/core/scene/photo/effects-photo/default-effects-photo-scene.stories.tsx +44 -0
  32. package/src/core/scene/photo/effects-photo/default-effects-photo-scene.tsx +352 -0
  33. package/src/core/scene/photo/effects-photo/index.ts +3 -0
  34. package/src/core/scene/photo/index.ts +25 -0
  35. package/src/core/scene/photo/photo-storybook.test.tsx +112 -0
  36. package/src/core/scene/slide/default-slide-scene-Horizontal.animated.stories.tsx +1 -1
  37. package/src/core/scene/slide/default-slide-scene-Horizontal.stories.tsx +1 -1
  38. package/src/core/scene/slide/default-slide-scene.animated.stories.tsx +1 -1
  39. package/src/core/scene/slide/default-slide-scene.stories.tsx +1 -1
  40. package/src/core/scene/video/default-video-scene-Horizontal.stories.tsx +26 -0
  41. package/src/core/scene/video/default-video-scene-Horizontal.tsx +20 -0
  42. package/src/core/scene/video/default-video-scene-Landscape.tsx +20 -0
  43. package/src/core/scene/video/default-video-scene.stories.tsx +26 -0
  44. package/src/core/scene/video/default-video-scene.tsx +283 -0
  45. package/src/core/scene/video/default-video-split-scene-Horizontal.tsx +20 -0
  46. package/src/core/scene/video/default-video-split-scene-Landscape.tsx +20 -0
  47. package/src/core/scene/video/default-video-split-scene.stories.tsx +41 -0
  48. package/src/core/scene/video/default-video-split-scene.tsx +19 -0
  49. package/src/core/scene/video/default-video-wall-scene-Horizontal.tsx +20 -0
  50. package/src/core/scene/video/default-video-wall-scene-Landscape.tsx +20 -0
  51. package/src/core/scene/video/default-video-wall-scene.stories.tsx +40 -0
  52. package/src/core/scene/video/default-video-wall-scene.tsx +19 -0
  53. package/src/core/scene/video/index.ts +9 -0
  54. package/src/storybook/remotion-preview.tsx +112 -6
@@ -0,0 +1,20 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+
5
+ import { DefaultVideoScene, componentMetadata as baseComponentMetadata } from './default-video-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultVideoSceneLandscape',
10
+ description: 'Video preview scene (Landscape 16:9 variant)',
11
+ sourceFile: 'src/core/scene/video/default-video-scene-Landscape.tsx',
12
+ aspectRatio: '16:9',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
14
+ }
15
+
16
+ export const DefaultVideoSceneLandscape = VideoComponent(componentMetadata)(function DefaultVideoSceneLandscape(
17
+ props: ComponentProps<typeof DefaultVideoScene>,
18
+ ) {
19
+ return <DefaultVideoScene {...props} />
20
+ })
@@ -0,0 +1,26 @@
1
+ import { DefaultVideoSceneLandscape } from './default-video-scene-Landscape'
2
+
3
+ const photoUrl = 'https://heyaai-statics-1342239856.cos.ap-guangzhou.myqcloud.com/tmp/premium_photo-1779404552087-0f905162376f-121.jpeg'
4
+ const frame = <img src={photoUrl} alt="Video preview" />
5
+
6
+ const meta = {
7
+ title: 'core/scene/video/DefaultVideoSceneLandscape',
8
+ component: DefaultVideoSceneLandscape,
9
+ args: {
10
+ title: 'Launch Cut',
11
+ subtitle: 'A single video preview with Apple-style framing and a soft editorial overlay.',
12
+ badge: 'Video',
13
+ runtimeLabel: '03:18',
14
+ },
15
+ }
16
+
17
+ export default meta
18
+
19
+ export const Default = {
20
+ render: (storyArgs: (typeof meta)['args']) => <DefaultVideoSceneLandscape {...storyArgs} video={frame} poster={frame} />,
21
+ parameters: { scenePreviewAspectRatio: '16:9' },
22
+ }
23
+ export const Animated = {
24
+ render: (storyArgs: (typeof meta)['args']) => <DefaultVideoSceneLandscape {...storyArgs} video={frame} poster={frame} />,
25
+ parameters: { scenePreviewAspectRatio: '16:9', scenePreviewMode: 'animation' },
26
+ }
@@ -0,0 +1,283 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { z } from 'zod'
4
+
5
+ import { VideoComponent, defineScenePluginMetadata } from '@vibecuting/component-project-helper'
6
+ import { cloneElement, isValidElement } from 'react'
7
+ import { spring, useCurrentFrame, useVideoConfig } from 'remotion'
8
+
9
+ import { BaseRemotionScene, SceneMotionLayer, type BaseRemotionSceneProps, useRemotionSceneRuntime, useSceneConfig } from '../../../base'
10
+ import { defineSceneComponent } from '../../../plugins/scene-component'
11
+
12
+ export interface DefaultVideoSceneProps extends BaseRemotionSceneProps {
13
+ sceneId?: string
14
+ sceneName?: string
15
+ title?: string
16
+ subtitle?: string
17
+ badge?: string
18
+ runtimeLabel?: string
19
+ video: ReactNode
20
+ videos?: ReactNode[]
21
+ poster?: ReactNode
22
+ variant?: 'player' | 'split' | 'wall'
23
+ accent?: string
24
+ secondaryAccent?: string
25
+ textColor?: string
26
+ }
27
+
28
+ export const componentMetadata = defineScenePluginMetadata({
29
+ resourceKind: 'scene',
30
+ name: 'DefaultVideoScene',
31
+ description: 'Video-first scene with poster-led motion and editorial framing',
32
+ sourceFile: 'src/core/scene/video/default-video-scene.tsx',
33
+ pluginKey: 'core.video.preview',
34
+ tags: ['scene', 'video', 'preview', 'player'],
35
+ aspectRatio: '16:9',
36
+ sceneType: 'scene',
37
+ sceneFamily: 'custom',
38
+ rootLayout: 'absolute-fill',
39
+ propsTypeName: 'DefaultVideoSceneProps',
40
+ })
41
+
42
+ function VideoFrame({
43
+ node,
44
+ isPortrait,
45
+ animationProgress,
46
+ }: {
47
+ node: ReactNode
48
+ isPortrait: boolean
49
+ animationProgress: number
50
+ }) {
51
+ return (
52
+ <div
53
+ style={{
54
+ width: '100%',
55
+ height: '100%',
56
+ overflow: 'hidden',
57
+ border: '1px solid rgba(255,255,255,0.14)',
58
+ background: 'rgba(2,6,23,0.86)',
59
+ transform: `scale(${1 + (1 - animationProgress) * 0.012})`,
60
+ }}
61
+ >
62
+ {node}
63
+ </div>
64
+ )
65
+ }
66
+
67
+ function renderCoverMedia(node: ReactNode) {
68
+ if (isValidElement(node)) {
69
+ const existingStyle = (node.props as { style?: Record<string, unknown> }).style ?? {}
70
+ return cloneElement(node, {
71
+ ...(node.props as Record<string, unknown>),
72
+ style: {
73
+ ...existingStyle,
74
+ width: '100%',
75
+ height: '100%',
76
+ objectFit: 'cover',
77
+ display: 'block',
78
+ minWidth: 0,
79
+ minHeight: 0,
80
+ },
81
+ })
82
+ }
83
+
84
+ return <div style={{ width: '100%', height: '100%', minWidth: 0, minHeight: 0 }}>{node}</div>
85
+ }
86
+
87
+ export const DefaultVideoScene = VideoComponent(componentMetadata)(
88
+ defineSceneComponent({
89
+ family: 'custom',
90
+ propsSchema: z.object({
91
+ sceneId: z.string().optional(),
92
+ sceneName: z.string().optional(),
93
+ title: z.string().optional(),
94
+ subtitle: z.string().optional(),
95
+ badge: z.string().optional(),
96
+ runtimeLabel: z.string().optional(),
97
+ video: z.any(),
98
+ videos: z.array(z.any()).optional(),
99
+ poster: z.any().optional(),
100
+ variant: z.enum(['player', 'split', 'wall']).optional(),
101
+ accent: z.string().optional(),
102
+ secondaryAccent: z.string().optional(),
103
+ textColor: z.string().optional(),
104
+ }),
105
+ component: function DefaultVideoScene({
106
+ id,
107
+ name,
108
+ sceneId,
109
+ sceneName,
110
+ title,
111
+ subtitle,
112
+ badge,
113
+ runtimeLabel,
114
+ video,
115
+ videos,
116
+ poster,
117
+ variant = 'player',
118
+ accent,
119
+ secondaryAccent,
120
+ textColor,
121
+ }: DefaultVideoSceneProps) {
122
+ const runtime = useRemotionSceneRuntime()
123
+ const sceneConfig = useSceneConfig()
124
+ const frame = useCurrentFrame()
125
+ const { fps } = useVideoConfig()
126
+ const isPortrait = runtime.height > runtime.width
127
+ const animationProgress =
128
+ sceneConfig.sceneType === 'animation'
129
+ ? spring({
130
+ frame,
131
+ fps,
132
+ config: {
133
+ damping: 18,
134
+ mass: 0.8,
135
+ stiffness: 110,
136
+ },
137
+ })
138
+ : 1
139
+ const titleSize = Math.round(runtime.height * (isPortrait ? 0.032 : 0.038))
140
+ const subtitleSize = Math.round(runtime.height * 0.017)
141
+ const frames = (videos?.length ? videos : [video]).slice(0, 4)
142
+
143
+ return (
144
+ <BaseRemotionScene
145
+ id={id ?? sceneId}
146
+ name={name ?? sceneName ?? title}
147
+ style={{
148
+ background: isPortrait
149
+ ? `linear-gradient(180deg, ${accent ?? '#020617'} 0%, #0f172a 55%, #000000 100%)`
150
+ : `radial-gradient(circle at 50% 24%, ${secondaryAccent ?? 'rgba(255,255,255,0.08)'} 0%, ${accent ?? '#020617'} 48%, #000000 100%)`,
151
+ color: textColor ?? '#f8fafc',
152
+ padding: Math.round(runtime.height * (isPortrait ? 0.02 : 0.03)),
153
+ }}
154
+ >
155
+ <section
156
+ style={{
157
+ width: '100%',
158
+ height: '100%',
159
+ display: 'grid',
160
+ gridTemplateRows: 'minmax(0, 1fr) auto',
161
+ gap: 16,
162
+ minWidth: 0,
163
+ minHeight: 0,
164
+ }}
165
+ >
166
+ {variant === 'wall' ? (
167
+ <div
168
+ style={{
169
+ display: 'grid',
170
+ gridTemplateColumns: isPortrait ? '1fr 1fr' : 'repeat(2, minmax(0, 1fr))',
171
+ gridTemplateRows: isPortrait ? 'repeat(2, minmax(0, 1fr))' : 'repeat(2, minmax(0, 1fr))',
172
+ gap: 12,
173
+ minWidth: 0,
174
+ minHeight: 0,
175
+ }}
176
+ >
177
+ {frames.map((item, index) => (
178
+ <SceneMotionLayer key={`video-wall-${index}`} variant="media" delayIndex={index}>
179
+ <VideoFrame node={item} isPortrait={isPortrait} animationProgress={animationProgress} />
180
+ </SceneMotionLayer>
181
+ ))}
182
+ </div>
183
+ ) : variant === 'split' ? (
184
+ <div
185
+ style={{
186
+ display: 'grid',
187
+ gridTemplateColumns: isPortrait ? '1fr' : 'minmax(0, 1.18fr) minmax(250px, 0.82fr)',
188
+ gap: 16,
189
+ minWidth: 0,
190
+ minHeight: 0,
191
+ }}
192
+ >
193
+ <SceneMotionLayer variant="panel">
194
+ <VideoFrame node={video} isPortrait={isPortrait} animationProgress={animationProgress} />
195
+ </SceneMotionLayer>
196
+ <div
197
+ style={{
198
+ display: 'grid',
199
+ gap: 12,
200
+ alignContent: 'start',
201
+ padding: isPortrait ? '0' : '8px 0',
202
+ minWidth: 0,
203
+ }}
204
+ >
205
+ <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'center' }}>
206
+ <div style={{ fontSize: Math.round(runtime.height * 0.014), letterSpacing: '0.24em', textTransform: 'uppercase', opacity: 0.58 }}>
207
+ {badge ?? 'Video'}
208
+ </div>
209
+ {runtimeLabel ? (
210
+ <div style={{ fontSize: Math.round(runtime.height * 0.014), letterSpacing: '0.18em', textTransform: 'uppercase', opacity: 0.58 }}>
211
+ {runtimeLabel}
212
+ </div>
213
+ ) : null}
214
+ </div>
215
+ {title ? (
216
+ <SceneMotionLayer variant="hero-title">
217
+ <div style={{ fontSize: titleSize, lineHeight: 0.96, letterSpacing: '-0.06em', fontWeight: 800 }}>{title}</div>
218
+ </SceneMotionLayer>
219
+ ) : null}
220
+ {subtitle ? <div style={{ fontSize: subtitleSize, lineHeight: 1.45, opacity: 0.84 }}>{subtitle}</div> : null}
221
+ <div style={{ border: '1px solid rgba(255,255,255,0.12)', background: 'rgba(2,6,23,0.56)', padding: 14, display: 'grid', gap: 10 }}>
222
+ <div style={{ fontSize: Math.round(runtime.height * 0.013), letterSpacing: '0.22em', textTransform: 'uppercase', opacity: 0.56 }}>Editorial Notes</div>
223
+ <div style={{ fontSize: Math.round(runtime.height * 0.018), lineHeight: 1.45, opacity: 0.88 }}>
224
+ Use this layout for review notes, chapter context, or a companion still beside the clip.
225
+ </div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ ) : (
230
+ <div
231
+ style={{
232
+ position: 'relative',
233
+ width: '100%',
234
+ height: '100%',
235
+ aspectRatio: isPortrait ? '9 / 16' : '16 / 9',
236
+ minWidth: 0,
237
+ minHeight: 0,
238
+ overflow: 'hidden',
239
+ border: '1px solid rgba(255,255,255,0.14)',
240
+ background: 'rgba(2,6,23,0.84)',
241
+ }}
242
+ >
243
+ <SceneMotionLayer
244
+ variant="media"
245
+ style={{
246
+ position: 'absolute',
247
+ inset: '-8%',
248
+ opacity: 0.26,
249
+ filter: 'blur(22px) saturate(1.06)',
250
+ transform: `scale(${1.12 + (1 - animationProgress) * 0.02})`,
251
+ }}
252
+ >
253
+ {renderCoverMedia(poster ?? video)}
254
+ </SceneMotionLayer>
255
+ <SceneMotionLayer variant="media" style={{ position: 'absolute', inset: 0 }}>
256
+ {renderCoverMedia(poster ?? video)}
257
+ </SceneMotionLayer>
258
+ <div
259
+ aria-hidden="true"
260
+ style={{
261
+ position: 'absolute',
262
+ inset: 0,
263
+ background: 'linear-gradient(180deg, rgba(2,6,23,0.1) 0%, rgba(2,6,23,0.2) 42%, rgba(2,6,23,0.72) 100%)',
264
+ }}
265
+ />
266
+ <div style={{ position: 'absolute', left: 16, top: 16, display: 'flex', gap: 10, alignItems: 'center' }}>
267
+ <div style={{ width: 10, height: 10, borderRadius: 9999, background: 'rgba(239,68,68,0.95)', boxShadow: '0 0 0 4px rgba(239,68,68,0.16)' }} />
268
+ <div style={{ fontSize: Math.round(runtime.height * 0.013), letterSpacing: '0.24em', textTransform: 'uppercase', opacity: 0.72 }}>
269
+ {badge ?? 'Video Preview'}
270
+ </div>
271
+ </div>
272
+ <div style={{ position: 'absolute', left: 16, right: 16, bottom: 16, display: 'grid', gap: 8 }}>
273
+ {title ? <div style={{ fontSize: titleSize, fontWeight: 800, letterSpacing: '-0.06em', lineHeight: 0.95 }}>{title}</div> : null}
274
+ {subtitle ? <div style={{ fontSize: subtitleSize, lineHeight: 1.45, opacity: 0.86, maxWidth: isPortrait ? '88%' : '60%' }}>{subtitle}</div> : null}
275
+ </div>
276
+ </div>
277
+ )}
278
+ </section>
279
+ </BaseRemotionScene>
280
+ )
281
+ },
282
+ }),
283
+ )
@@ -0,0 +1,20 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+
5
+ import { DefaultVideoSplitScene, componentMetadata as baseComponentMetadata } from './default-video-split-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultVideoSplitSceneHorizontal',
10
+ description: 'Video split preview scene (Portrait 9:16 variant)',
11
+ sourceFile: 'src/core/scene/video/default-video-split-scene-Horizontal.tsx',
12
+ aspectRatio: '9:16',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
14
+ }
15
+
16
+ export const DefaultVideoSplitSceneHorizontal = VideoComponent(componentMetadata)(function DefaultVideoSplitSceneHorizontal(
17
+ props: ComponentProps<typeof DefaultVideoSplitScene>,
18
+ ) {
19
+ return <DefaultVideoSplitScene {...props} />
20
+ })
@@ -0,0 +1,20 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+
5
+ import { DefaultVideoSplitScene, componentMetadata as baseComponentMetadata } from './default-video-split-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultVideoSplitSceneLandscape',
10
+ description: 'Video split preview scene (Landscape 16:9 variant)',
11
+ sourceFile: 'src/core/scene/video/default-video-split-scene-Landscape.tsx',
12
+ aspectRatio: '16:9',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
14
+ }
15
+
16
+ export const DefaultVideoSplitSceneLandscape = VideoComponent(componentMetadata)(function DefaultVideoSplitSceneLandscape(
17
+ props: ComponentProps<typeof DefaultVideoSplitScene>,
18
+ ) {
19
+ return <DefaultVideoSplitScene {...props} />
20
+ })
@@ -0,0 +1,41 @@
1
+ import { DefaultVideoSplitSceneLandscape } from './default-video-split-scene-Landscape'
2
+ import { DefaultVideoSplitSceneHorizontal } from './default-video-split-scene-Horizontal'
3
+
4
+ const frame = (
5
+ <div
6
+ style={{
7
+ width: '100%',
8
+ height: '100%',
9
+ background:
10
+ 'linear-gradient(180deg, rgba(255,255,255,0.18), rgba(2,6,23,0.84)), linear-gradient(135deg, #0f766e 0%, #1d4ed8 52%, #020617 100%)',
11
+ }}
12
+ />
13
+ )
14
+
15
+ const args = {
16
+ title: 'Editorial Split',
17
+ subtitle: 'Use this when the clip needs a companion still, notes, or a second framing column.',
18
+ badge: 'Split',
19
+ runtimeLabel: '02:24',
20
+ video: frame,
21
+ poster: frame,
22
+ }
23
+
24
+ const meta = {
25
+ title: 'core/scene/video/DefaultVideoSplitSceneLandscape',
26
+ component: DefaultVideoSplitSceneLandscape,
27
+ args,
28
+ }
29
+
30
+ export default meta
31
+
32
+ export const Default = { parameters: { scenePreviewAspectRatio: '16:9' } }
33
+ export const Animated = { parameters: { scenePreviewAspectRatio: '16:9', scenePreviewMode: 'animation' } }
34
+ export const Horizontal = {
35
+ render: (storyArgs: typeof args) => <DefaultVideoSplitSceneHorizontal {...storyArgs} />,
36
+ parameters: { scenePreviewAspectRatio: '9:16' },
37
+ }
38
+ export const HorizontalAnimated = {
39
+ render: (storyArgs: typeof args) => <DefaultVideoSplitSceneHorizontal {...storyArgs} />,
40
+ parameters: { scenePreviewAspectRatio: '9:16', scenePreviewMode: 'animation' },
41
+ }
@@ -0,0 +1,19 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+
5
+ import { DefaultVideoScene, componentMetadata as baseComponentMetadata } from './default-video-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultVideoSplitScene',
10
+ description: 'Video split preview scene',
11
+ sourceFile: 'src/core/scene/video/default-video-split-scene.tsx',
12
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'split'])),
13
+ }
14
+
15
+ export const DefaultVideoSplitScene = VideoComponent(componentMetadata)(function DefaultVideoSplitScene(
16
+ props: ComponentProps<typeof DefaultVideoScene>,
17
+ ) {
18
+ return <DefaultVideoScene {...props} variant="split" />
19
+ })
@@ -0,0 +1,20 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+
5
+ import { DefaultVideoWallScene, componentMetadata as baseComponentMetadata } from './default-video-wall-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultVideoWallSceneHorizontal',
10
+ description: 'Video wall preview scene (Portrait 9:16 variant)',
11
+ sourceFile: 'src/core/scene/video/default-video-wall-scene-Horizontal.tsx',
12
+ aspectRatio: '9:16',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
14
+ }
15
+
16
+ export const DefaultVideoWallSceneHorizontal = VideoComponent(componentMetadata)(function DefaultVideoWallSceneHorizontal(
17
+ props: ComponentProps<typeof DefaultVideoWallScene>,
18
+ ) {
19
+ return <DefaultVideoWallScene {...props} />
20
+ })
@@ -0,0 +1,20 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+
5
+ import { DefaultVideoWallScene, componentMetadata as baseComponentMetadata } from './default-video-wall-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultVideoWallSceneLandscape',
10
+ description: 'Video wall preview scene (Landscape 16:9 variant)',
11
+ sourceFile: 'src/core/scene/video/default-video-wall-scene-Landscape.tsx',
12
+ aspectRatio: '16:9',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
14
+ }
15
+
16
+ export const DefaultVideoWallSceneLandscape = VideoComponent(componentMetadata)(function DefaultVideoWallSceneLandscape(
17
+ props: ComponentProps<typeof DefaultVideoWallScene>,
18
+ ) {
19
+ return <DefaultVideoWallScene {...props} />
20
+ })
@@ -0,0 +1,40 @@
1
+ import { DefaultVideoWallSceneHorizontal } from './default-video-wall-scene-Horizontal'
2
+ import { DefaultVideoWallSceneLandscape } from './default-video-wall-scene-Landscape'
3
+
4
+ const frame = (
5
+ <div
6
+ style={{
7
+ width: '100%',
8
+ height: '100%',
9
+ background:
10
+ 'linear-gradient(180deg, rgba(255,255,255,0.16), rgba(2,6,23,0.84)), linear-gradient(135deg, #7c3aed 0%, #1d4ed8 42%, #020617 100%)',
11
+ }}
12
+ />
13
+ )
14
+
15
+ const args = {
16
+ title: 'Clip Wall',
17
+ subtitle: 'Four clips arranged like a screening board or trailer review wall.',
18
+ badge: 'Wall',
19
+ video: frame,
20
+ videos: [frame, frame, frame, frame],
21
+ }
22
+
23
+ const meta = {
24
+ title: 'core/scene/video/DefaultVideoWallSceneLandscape',
25
+ component: DefaultVideoWallSceneLandscape,
26
+ args,
27
+ }
28
+
29
+ export default meta
30
+
31
+ export const Default = { parameters: { scenePreviewAspectRatio: '16:9' } }
32
+ export const Animated = { parameters: { scenePreviewAspectRatio: '16:9', scenePreviewMode: 'animation' } }
33
+ export const Horizontal = {
34
+ render: (storyArgs: typeof args) => <DefaultVideoWallSceneHorizontal {...storyArgs} />,
35
+ parameters: { scenePreviewAspectRatio: '9:16' },
36
+ }
37
+ export const HorizontalAnimated = {
38
+ render: (storyArgs: typeof args) => <DefaultVideoWallSceneHorizontal {...storyArgs} />,
39
+ parameters: { scenePreviewAspectRatio: '9:16', scenePreviewMode: 'animation' },
40
+ }
@@ -0,0 +1,19 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+
5
+ import { DefaultVideoScene, componentMetadata as baseComponentMetadata } from './default-video-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultVideoWallScene',
10
+ description: 'Video wall preview scene',
11
+ sourceFile: 'src/core/scene/video/default-video-wall-scene.tsx',
12
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'wall', 'multi'])),
13
+ }
14
+
15
+ export const DefaultVideoWallScene = VideoComponent(componentMetadata)(function DefaultVideoWallScene(
16
+ props: ComponentProps<typeof DefaultVideoScene>,
17
+ ) {
18
+ return <DefaultVideoScene {...props} variant="wall" />
19
+ })
@@ -0,0 +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'