@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 { DefaultEffectsPhotoScene, componentMetadata as baseComponentMetadata } from './default-effects-photo-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultEffectsPhotoSceneHorizontal',
10
+ description: 'Photo effects scene for 9:16 portrait previews with a vertical layout',
11
+ sourceFile: 'src/core/scene/photo/effects-photo/default-effects-photo-scene-Horizontal.tsx',
12
+ aspectRatio: '9:16',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
14
+ }
15
+
16
+ export const DefaultEffectsPhotoSceneHorizontal = VideoComponent(componentMetadata)(function DefaultEffectsPhotoSceneHorizontal(
17
+ props: ComponentProps<typeof DefaultEffectsPhotoScene>,
18
+ ) {
19
+ return <DefaultEffectsPhotoScene {...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 { DefaultEffectsPhotoScene, componentMetadata as baseComponentMetadata } from './default-effects-photo-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultEffectsPhotoSceneLandscape',
10
+ description: 'Photo effects scene for 16:9 landscape previews with a wide cinematic layout',
11
+ sourceFile: 'src/core/scene/photo/effects-photo/default-effects-photo-scene-Landscape.tsx',
12
+ aspectRatio: '16:9',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
14
+ }
15
+
16
+ export const DefaultEffectsPhotoSceneLandscape = VideoComponent(componentMetadata)(function DefaultEffectsPhotoSceneLandscape(
17
+ props: ComponentProps<typeof DefaultEffectsPhotoScene>,
18
+ ) {
19
+ return <DefaultEffectsPhotoScene {...props} />
20
+ })
@@ -0,0 +1,44 @@
1
+ import { DefaultEffectsPhotoSceneLandscape } from './default-effects-photo-scene-Landscape'
2
+
3
+ const photoUrl = 'https://heyaai-statics-1342239856.cos.ap-guangzhou.myqcloud.com/tmp/premium_photo-1779404552087-0f905162376f-121.jpeg'
4
+
5
+ const meta = {
6
+ title: 'core/scene/photo/effects-photo/DefaultEffectsPhotoSceneLandscape',
7
+ component: DefaultEffectsPhotoSceneLandscape,
8
+ args: {
9
+ title: 'Golden Echo',
10
+ subtitle: 'Layered photo grading with glow, grain, warmth and a cinematic vignette.',
11
+ badge: 'Effects Photo',
12
+ imageSrc: photoUrl,
13
+ effects: [
14
+ { type: 'glow', intensity: 0.55 },
15
+ { type: 'vignette', intensity: 0.65 },
16
+ { type: 'grain', intensity: 0.12 },
17
+ { type: 'warmth', intensity: 0.18, hue: '#f59e0b' },
18
+ ],
19
+ accent: '#020617',
20
+ secondaryAccent: '#1d4ed8',
21
+ },
22
+ }
23
+
24
+ export default meta
25
+
26
+ export const Default = {
27
+ parameters: {
28
+ scenePreviewAspectRatio: '16:9',
29
+ },
30
+ }
31
+
32
+ export const Animated = {
33
+ parameters: {
34
+ scenePreviewAspectRatio: '16:9',
35
+ scenePreviewMode: 'animation',
36
+ },
37
+ }
38
+
39
+ export const Both = {
40
+ parameters: {
41
+ scenePreviewAspectRatio: '16:9',
42
+ scenePreviewMode: 'both',
43
+ },
44
+ }
@@ -0,0 +1,352 @@
1
+ import { useMemo } from 'react'
2
+
3
+ import { z } from 'zod'
4
+
5
+ import { VideoComponent, defineScenePluginMetadata } from '@vibecuting/component-project-helper'
6
+ import { Easing, Img, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion'
7
+
8
+ import { BaseRemotionScene, type BaseRemotionSceneProps, useRemotionSceneRuntime, useSceneConfig } from '../../../../base'
9
+ import { defineSceneComponent } from '../../../../plugins/scene-component'
10
+
11
+ export type PhotoEffectType = 'vignette' | 'grain' | 'glow' | 'warmth' | 'soft-focus'
12
+
13
+ export interface PhotoEffect {
14
+ type: PhotoEffectType
15
+ intensity?: number
16
+ hue?: string
17
+ }
18
+
19
+ export interface DefaultEffectsPhotoSceneProps extends BaseRemotionSceneProps {
20
+ sceneId?: string
21
+ sceneName?: string
22
+ title?: string
23
+ subtitle?: string
24
+ badge?: string
25
+ imageSrc: string
26
+ effects?: PhotoEffect[]
27
+ accent?: string
28
+ secondaryAccent?: string
29
+ textColor?: string
30
+ }
31
+
32
+ export const componentMetadata = defineScenePluginMetadata({
33
+ resourceKind: 'scene',
34
+ name: 'DefaultEffectsPhotoScene',
35
+ description: 'Photo scene with layered Remotion effects and cinematic grading',
36
+ sourceFile: 'src/core/scene/photo/effects-photo/default-effects-photo-scene.tsx',
37
+ pluginKey: 'core.photo.effects-photo',
38
+ tags: ['scene', 'photo', 'effects', 'image', 'cinematic'],
39
+ aspectRatio: '16:9',
40
+ sceneType: 'scene',
41
+ sceneFamily: 'custom',
42
+ rootLayout: 'absolute-fill',
43
+ propsTypeName: 'DefaultEffectsPhotoSceneProps',
44
+ })
45
+
46
+ function matchesEffect(effects: PhotoEffect[] | undefined, type: PhotoEffectType) {
47
+ return effects?.some((effect) => effect.type === type) ?? false
48
+ }
49
+
50
+ function effectIntensity(effects: PhotoEffect[] | undefined, type: PhotoEffectType, fallback: number) {
51
+ return effects?.find((effect) => effect.type === type)?.intensity ?? fallback
52
+ }
53
+
54
+ export const DefaultEffectsPhotoScene = VideoComponent(componentMetadata)(
55
+ defineSceneComponent({
56
+ family: 'custom',
57
+ propsSchema: z.object({
58
+ sceneId: z.string().optional(),
59
+ sceneName: z.string().optional(),
60
+ title: z.string().optional(),
61
+ subtitle: z.string().optional(),
62
+ badge: z.string().optional(),
63
+ imageSrc: z.string(),
64
+ effects: z
65
+ .array(
66
+ z.object({
67
+ type: z.enum(['vignette', 'grain', 'glow', 'warmth', 'soft-focus']),
68
+ intensity: z.number().min(0).max(1).optional(),
69
+ hue: z.string().optional(),
70
+ }),
71
+ )
72
+ .optional(),
73
+ accent: z.string().optional(),
74
+ secondaryAccent: z.string().optional(),
75
+ textColor: z.string().optional(),
76
+ }),
77
+ component: function DefaultEffectsPhotoScene({
78
+ id,
79
+ name,
80
+ sceneId,
81
+ sceneName,
82
+ title,
83
+ subtitle,
84
+ badge,
85
+ imageSrc,
86
+ effects,
87
+ accent,
88
+ secondaryAccent,
89
+ textColor,
90
+ }: DefaultEffectsPhotoSceneProps) {
91
+ const runtime = useRemotionSceneRuntime()
92
+ const sceneConfig = useSceneConfig()
93
+ const frame = useCurrentFrame()
94
+ const { fps } = useVideoConfig()
95
+ const portrait = runtime.height > runtime.width
96
+ const animated = sceneConfig.sceneType === 'animation'
97
+ const animationProgress =
98
+ sceneConfig.sceneType === 'animation'
99
+ ? spring({
100
+ frame,
101
+ fps,
102
+ config: { damping: 22, mass: 0.8, stiffness: 130 },
103
+ })
104
+ : 1
105
+ const reveal = sceneConfig.sceneType === 'animation' ? spring({ frame, fps, delay: 6, config: { damping: 20, mass: 0.7, stiffness: 150 } }) : 1
106
+ const focusDrift = sceneConfig.sceneType === 'animation' ? Math.sin((frame / fps) * Math.PI * 0.75) : 0
107
+ const scale = 1.01 + (1 - animationProgress) * 0.06 + focusDrift * 0.008
108
+ const translateX = (1 - animationProgress) * (portrait ? -8 : -12) + focusDrift * (portrait ? 8 : 12)
109
+ const translateY = (1 - animationProgress) * 10 + focusDrift * 16
110
+ const titleSize = Math.round(runtime.height * (portrait ? 0.034 : 0.04))
111
+ const subtitleSize = Math.round(runtime.height * 0.017)
112
+ const framePaddingX = Math.round(runtime.width * (portrait ? 0.015 : 0.014))
113
+ const framePaddingY = Math.round(runtime.height * (portrait ? 0.014 : 0.012))
114
+ const frameLeft = framePaddingX
115
+ const frameTop = framePaddingY
116
+ const frameWidth = Math.max(1, runtime.width - framePaddingX * 2)
117
+ const frameHeight = Math.max(1, runtime.height - framePaddingY * 2)
118
+ const imageFilter = animated ? 'brightness(1.08) contrast(1.04) saturate(1.08)' : 'brightness(1.9) contrast(1.18) saturate(1.22)'
119
+ const vignette = matchesEffect(effects, 'vignette')
120
+ const grain = matchesEffect(effects, 'grain')
121
+ const glow = matchesEffect(effects, 'glow')
122
+ const warmth = matchesEffect(effects, 'warmth')
123
+ const softFocus = matchesEffect(effects, 'soft-focus')
124
+ const vignetteOpacity = effectIntensity(effects, 'vignette', 0.52) * (0.65 + reveal * 0.35)
125
+ const glowOpacity = effectIntensity(effects, 'glow', 0.45) * (0.7 + reveal * 0.3)
126
+ const grainOpacity = effectIntensity(effects, 'grain', 0.12)
127
+ const warmthHue = effects?.find((effect) => effect.type === 'warmth')?.hue ?? accent ?? '#f97316'
128
+ const focusOverlay = interpolate(reveal, [0, 1], [0, 1], {
129
+ easing: Easing.inOut(Easing.quad),
130
+ extrapolateLeft: 'clamp',
131
+ extrapolateRight: 'clamp',
132
+ })
133
+
134
+ const effectLayers = useMemo(
135
+ () => (
136
+ <>
137
+ {glow ? (
138
+ <div
139
+ aria-hidden="true"
140
+ style={{
141
+ position: 'absolute',
142
+ inset: '4%',
143
+ opacity: glowOpacity * 0.55,
144
+ filter: 'blur(20px) saturate(1.18) brightness(0.98)',
145
+ transform: `scale(${1.03 + (1 - animationProgress) * 0.02})`,
146
+ pointerEvents: 'none',
147
+ }}
148
+ >
149
+ <Img
150
+ src={imageSrc}
151
+ alt={title ?? 'Effects photo'}
152
+ style={{
153
+ width: '100%',
154
+ height: '100%',
155
+ objectFit: 'cover',
156
+ }}
157
+ />
158
+ </div>
159
+ ) : null}
160
+ {softFocus ? (
161
+ <div
162
+ aria-hidden="true"
163
+ style={{
164
+ position: 'absolute',
165
+ inset: 0,
166
+ opacity: 0.18 + focusOverlay * 0.08,
167
+ filter: 'blur(10px) saturate(1.04)',
168
+ transform: `scale(${1.02 + focusOverlay * 0.01})`,
169
+ pointerEvents: 'none',
170
+ }}
171
+ >
172
+ <Img
173
+ src={imageSrc}
174
+ alt={title ?? 'Effects photo'}
175
+ style={{
176
+ width: '100%',
177
+ height: '100%',
178
+ objectFit: 'cover',
179
+ }}
180
+ />
181
+ </div>
182
+ ) : null}
183
+ {vignette ? (
184
+ <div
185
+ aria-hidden="true"
186
+ style={{
187
+ position: 'absolute',
188
+ inset: 0,
189
+ background:
190
+ 'radial-gradient(circle at center, rgba(255,255,255,0.04) 0%, rgba(255,255,255,0.01) 28%, rgba(2,6,23,0.14) 56%, rgba(2,6,23,0.46) 100%)',
191
+ opacity: vignetteOpacity * 0.72,
192
+ pointerEvents: 'none',
193
+ }}
194
+ />
195
+ ) : null}
196
+ {warmth ? (
197
+ <div
198
+ aria-hidden="true"
199
+ style={{
200
+ position: 'absolute',
201
+ inset: 0,
202
+ background: `linear-gradient(135deg, ${warmthHue} 0%, rgba(255,255,255,0.02) 36%, ${secondaryAccent ?? 'rgba(59,130,246,0.14)'} 100%)`,
203
+ mixBlendMode: 'screen',
204
+ opacity: 0.08 + focusOverlay * 0.08,
205
+ pointerEvents: 'none',
206
+ }}
207
+ />
208
+ ) : null}
209
+ {grain ? (
210
+ <div
211
+ aria-hidden="true"
212
+ style={{
213
+ position: 'absolute',
214
+ inset: 0,
215
+ opacity: grainOpacity,
216
+ backgroundImage:
217
+ 'radial-gradient(rgba(255,255,255,0.35) 0.7px, transparent 0.7px), radial-gradient(rgba(0,0,0,0.3) 0.7px, transparent 0.7px)',
218
+ backgroundSize: '4px 4px, 4px 4px',
219
+ backgroundPosition: '0 0, 2px 2px',
220
+ mixBlendMode: 'soft-light',
221
+ pointerEvents: 'none',
222
+ }}
223
+ />
224
+ ) : null}
225
+ </>
226
+ ),
227
+ [
228
+ animationProgress,
229
+ focusOverlay,
230
+ grain,
231
+ grainOpacity,
232
+ glow,
233
+ glowOpacity,
234
+ imageSrc,
235
+ secondaryAccent,
236
+ softFocus,
237
+ title,
238
+ vignette,
239
+ vignetteOpacity,
240
+ warmth,
241
+ warmthHue,
242
+ ],
243
+ )
244
+
245
+ return (
246
+ <BaseRemotionScene
247
+ id={id ?? sceneId}
248
+ name={name ?? sceneName ?? title}
249
+ style={{
250
+ background:
251
+ portrait
252
+ ? `linear-gradient(180deg, ${accent ?? '#020617'} 0%, #000000 100%)`
253
+ : `radial-gradient(circle at 50% 18%, ${secondaryAccent ?? 'rgba(255,255,255,0.08)'} 0%, ${accent ?? '#020617'} 52%, #000000 100%)`,
254
+ color: textColor ?? '#f8fafc',
255
+ padding: Math.round(runtime.height * (portrait ? 0.02 : 0.03)),
256
+ }}
257
+ >
258
+ <div
259
+ style={{
260
+ position: 'absolute',
261
+ inset: 0,
262
+ overflow: 'hidden',
263
+ border: '1px solid rgba(255,255,255,0.1)',
264
+ background: 'rgba(2,6,23,0.82)',
265
+ isolation: 'isolate',
266
+ }}
267
+ >
268
+ <div
269
+ style={{
270
+ position: 'absolute',
271
+ top: frameTop,
272
+ left: frameLeft,
273
+ width: frameWidth,
274
+ height: frameHeight,
275
+ border: animated ? '1px solid rgba(255,255,255,0.1)' : '1px solid rgba(255,255,255,0.16)',
276
+ boxShadow: animated ? '0 18px 56px rgba(0,0,0,0.24)' : '0 12px 32px rgba(0,0,0,0.14)',
277
+ backgroundColor: animated ? 'rgba(2,6,23,0.14)' : 'rgba(248,250,252,0.06)',
278
+ backgroundImage: `url(${imageSrc})`,
279
+ backgroundSize: 'cover',
280
+ backgroundPosition: `${50}% ${portrait ? 44 : 52}%`,
281
+ backgroundRepeat: 'no-repeat',
282
+ overflow: 'hidden',
283
+ filter: imageFilter,
284
+ }}
285
+ >
286
+ </div>
287
+
288
+ <div
289
+ style={{
290
+ position: 'absolute',
291
+ top: frameTop,
292
+ left: frameLeft,
293
+ width: frameWidth,
294
+ height: frameHeight,
295
+ }}
296
+ >
297
+ {effectLayers}
298
+ </div>
299
+
300
+ <div
301
+ aria-hidden="true"
302
+ style={{
303
+ position: 'absolute',
304
+ inset: 0,
305
+ background:
306
+ 'linear-gradient(180deg, rgba(2,6,23,0.04) 0%, rgba(2,6,23,0.12) 38%, rgba(2,6,23,0.52) 100%)',
307
+ pointerEvents: 'none',
308
+ }}
309
+ />
310
+
311
+ <div
312
+ style={{
313
+ position: 'absolute',
314
+ inset: 0,
315
+ display: 'grid',
316
+ alignContent: 'space-between',
317
+ padding: Math.round(runtime.height * (portrait ? 0.02 : 0.03)),
318
+ zIndex: 2,
319
+ pointerEvents: 'none',
320
+ }}
321
+ >
322
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 12 }}>
323
+ <div
324
+ style={{
325
+ display: 'inline-flex',
326
+ alignItems: 'center',
327
+ gap: 8,
328
+ border: '1px solid rgba(255,255,255,0.18)',
329
+ background: 'rgba(2,6,23,0.6)',
330
+ color: '#f8fafc',
331
+ padding: '8px 12px',
332
+ letterSpacing: '0.22em',
333
+ textTransform: 'uppercase',
334
+ fontSize: 11,
335
+ lineHeight: 1,
336
+ }}
337
+ >
338
+ {badge ?? 'Effects Photo'}
339
+ </div>
340
+ </div>
341
+
342
+ <div style={{ display: 'grid', gap: 8, maxWidth: portrait ? '86%' : '58%' }}>
343
+ {title ? <div style={{ fontSize: titleSize, fontWeight: 800, letterSpacing: '-0.06em', lineHeight: 0.94 }}>{title}</div> : null}
344
+ {subtitle ? <div style={{ fontSize: subtitleSize, lineHeight: 1.45, opacity: 0.84 }}>{subtitle}</div> : null}
345
+ </div>
346
+ </div>
347
+ </div>
348
+ </BaseRemotionScene>
349
+ )
350
+ },
351
+ }),
352
+ )
@@ -0,0 +1,3 @@
1
+ export { DefaultEffectsPhotoScene, componentMetadata as DefaultEffectsPhotoSceneMetadata } from './default-effects-photo-scene'
2
+ export { DefaultEffectsPhotoSceneLandscape, componentMetadata as DefaultEffectsPhotoSceneLandscapeMetadata } from './default-effects-photo-scene-Landscape'
3
+ export { DefaultEffectsPhotoSceneHorizontal, componentMetadata as DefaultEffectsPhotoSceneHorizontalMetadata } from './default-effects-photo-scene-Horizontal'
@@ -0,0 +1,25 @@
1
+ export { DefaultLivePhotoScene, componentMetadata as DefaultLivePhotoSceneMetadata } from './default-live-photo-scene'
2
+ export { DefaultLivePhotoSceneLandscape, componentMetadata as DefaultLivePhotoSceneLandscapeMetadata } from './default-live-photo-scene-Landscape'
3
+ export { DefaultLivePhotoSceneHorizontal, componentMetadata as DefaultLivePhotoSceneHorizontalMetadata } from './default-live-photo-scene-Horizontal'
4
+ export { DefaultPhotoWallScene, componentMetadata as DefaultPhotoWallSceneMetadata } from './default-photo-wall-scene'
5
+ export { DefaultPhotoWallSceneLandscape, componentMetadata as DefaultPhotoWallSceneLandscapeMetadata } from './default-photo-wall-scene-Landscape'
6
+ export { DefaultPhotoWallSceneHorizontal, componentMetadata as DefaultPhotoWallSceneHorizontalMetadata } from './default-photo-wall-scene-Horizontal'
7
+ export { DefaultPhotoStripScene, componentMetadata as DefaultPhotoStripSceneMetadata } from './default-photo-strip-scene'
8
+ export { DefaultPhotoStripSceneLandscape, componentMetadata as DefaultPhotoStripSceneLandscapeMetadata } from './default-photo-strip-scene-Landscape'
9
+ export { DefaultPhotoStripSceneHorizontal, componentMetadata as DefaultPhotoStripSceneHorizontalMetadata } from './default-photo-strip-scene-Horizontal'
10
+ export {
11
+ DefaultEffectsPhotoScene,
12
+ DefaultEffectsPhotoSceneLandscape,
13
+ DefaultEffectsPhotoSceneHorizontal,
14
+ DefaultEffectsPhotoSceneMetadata,
15
+ DefaultEffectsPhotoSceneLandscapeMetadata,
16
+ DefaultEffectsPhotoSceneHorizontalMetadata,
17
+ } from './effects-photo'
18
+ export {
19
+ Default3DVRPhotoScene,
20
+ Default3DVRPhotoSceneLandscape,
21
+ Default3DVRPhotoSceneHorizontal,
22
+ Default3DVRPhotoSceneMetadata,
23
+ Default3DVRPhotoSceneLandscapeMetadata,
24
+ Default3DVRPhotoSceneHorizontalMetadata,
25
+ } from './3dvr-photo'
@@ -0,0 +1,112 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ vi.mock('remotion', async () => import('../../../storybook/remotion-preview'))
4
+
5
+ import { SceneViewport } from '../../../storybook/SceneViewport'
6
+ import { Default3DVRPhotoSceneLandscape } from './3dvr-photo/default-3dvr-photo-scene-Landscape'
7
+ import { DefaultEffectsPhotoSceneLandscape } from './effects-photo/default-effects-photo-scene-Landscape'
8
+
9
+ const photoUrl = 'https://heyaai-statics-1342239856.cos.ap-guangzhou.myqcloud.com/tmp/premium_photo-1779404552087-0f905162376f-121.jpeg'
10
+ const { renderToStaticMarkup } = require('/data/projects/foldspace-stack-apps/vibecuting/packages/@vibecuting/video-project-core/node_modules/.pnpm/react-dom@19.2.7_react@19.2.7/node_modules/react-dom/server')
11
+
12
+ describe('photo scene storybook rendering', () => {
13
+ it('renders 3dvr-photo in storybook preview', () => {
14
+ const html = renderToStaticMarkup(
15
+ <SceneViewport width={1280} height={720} aspectRatio="16:9" mode="static">
16
+ <Default3DVRPhotoSceneLandscape
17
+ title="Stereo Window"
18
+ subtitle="3D camera controls over a still frame with subtle orbit, pitch and depth."
19
+ badge="3D VR Photo"
20
+ imageSrc={photoUrl}
21
+ yaw={12}
22
+ pitch={4}
23
+ roll={0}
24
+ perspective={980}
25
+ zoom={1}
26
+ depth={26}
27
+ focusX={0.5}
28
+ focusY={0.45}
29
+ accent="#020617"
30
+ secondaryAccent="#1d4ed8"
31
+ />
32
+ </SceneViewport>,
33
+ )
34
+
35
+ expect(html).toContain('Stereo Window')
36
+ expect(html).toContain(photoUrl)
37
+ })
38
+
39
+ it('renders 3dvr-photo animation preview in storybook preview', () => {
40
+ const html = renderToStaticMarkup(
41
+ <SceneViewport width={1280} height={720} aspectRatio="16:9" mode="animation">
42
+ <Default3DVRPhotoSceneLandscape
43
+ title="Stereo Window"
44
+ subtitle="3D camera controls over a still frame with subtle orbit, pitch and depth."
45
+ badge="3D VR Photo"
46
+ imageSrc={photoUrl}
47
+ yaw={12}
48
+ pitch={4}
49
+ roll={0}
50
+ perspective={980}
51
+ zoom={1}
52
+ depth={26}
53
+ focusX={0.5}
54
+ focusY={0.45}
55
+ accent="#020617"
56
+ secondaryAccent="#1d4ed8"
57
+ />
58
+ </SceneViewport>,
59
+ )
60
+
61
+ expect(html).toContain('Stereo Window')
62
+ expect(html).toContain(photoUrl)
63
+ })
64
+
65
+ it('renders effects-photo in storybook preview', () => {
66
+ const html = renderToStaticMarkup(
67
+ <SceneViewport width={1280} height={720} aspectRatio="16:9" mode="static">
68
+ <DefaultEffectsPhotoSceneLandscape
69
+ title="Golden Echo"
70
+ subtitle="Layered photo grading with glow, grain, warmth and a cinematic vignette."
71
+ badge="Effects Photo"
72
+ imageSrc={photoUrl}
73
+ effects={[
74
+ { type: 'glow', intensity: 0.55 },
75
+ { type: 'vignette', intensity: 0.65 },
76
+ { type: 'grain', intensity: 0.12 },
77
+ { type: 'warmth', intensity: 0.18, hue: '#f59e0b' },
78
+ ]}
79
+ accent="#020617"
80
+ secondaryAccent="#1d4ed8"
81
+ />
82
+ </SceneViewport>,
83
+ )
84
+
85
+ expect(html).toContain('Golden Echo')
86
+ expect(html).toContain(photoUrl)
87
+ })
88
+
89
+ it('renders effects-photo animation preview in storybook preview', () => {
90
+ const html = renderToStaticMarkup(
91
+ <SceneViewport width={1280} height={720} aspectRatio="16:9" mode="animation">
92
+ <DefaultEffectsPhotoSceneLandscape
93
+ title="Golden Echo"
94
+ subtitle="Layered photo grading with glow, grain, warmth and a cinematic vignette."
95
+ badge="Effects Photo"
96
+ imageSrc={photoUrl}
97
+ effects={[
98
+ { type: 'glow', intensity: 0.55 },
99
+ { type: 'vignette', intensity: 0.65 },
100
+ { type: 'grain', intensity: 0.12 },
101
+ { type: 'warmth', intensity: 0.18, hue: '#f59e0b' },
102
+ ]}
103
+ accent="#020617"
104
+ secondaryAccent="#1d4ed8"
105
+ />
106
+ </SceneViewport>,
107
+ )
108
+
109
+ expect(html).toContain('Golden Echo')
110
+ expect(html).toContain(photoUrl)
111
+ })
112
+ })
@@ -1,7 +1,7 @@
1
1
  import { DefaultSlideSceneHorizontal } from './default-slide-scene-Horizontal'
2
2
 
3
3
  const meta = {
4
- title: 'core/scene/DefaultSlideSceneHorizontal/Animated',
4
+ title: 'core/scene/slide/default-slide-scene/DefaultSlideSceneHorizontal/Animated',
5
5
  component: DefaultSlideSceneHorizontal,
6
6
  args: {
7
7
  title: 'Slide Scene',
@@ -1,7 +1,7 @@
1
1
  import { DefaultSlideSceneHorizontal } from './default-slide-scene-Horizontal'
2
2
 
3
3
  const meta = {
4
- title: 'core/scene/DefaultSlideSceneHorizontal',
4
+ title: 'core/scene/slide/default-slide-scene/DefaultSlideSceneHorizontal',
5
5
  component: DefaultSlideSceneHorizontal,
6
6
  args: {
7
7
  title: 'Slide Scene',
@@ -1,7 +1,7 @@
1
1
  import { DefaultSlideSceneLandscape } from './default-slide-scene-Landscape'
2
2
 
3
3
  const meta = {
4
- title: 'core/scene/DefaultSlideSceneLandscape/Animated',
4
+ title: 'core/scene/slide/default-slide-scene/DefaultSlideSceneLandscape/Animated',
5
5
  component: DefaultSlideSceneLandscape,
6
6
  args: {
7
7
  title: 'Slide Scene',
@@ -1,7 +1,7 @@
1
1
  import { DefaultSlideSceneLandscape } from './default-slide-scene-Landscape'
2
2
 
3
3
  const meta = {
4
- title: 'core/scene/DefaultSlideSceneLandscape',
4
+ title: 'core/scene/slide/default-slide-scene/DefaultSlideSceneLandscape',
5
5
  component: DefaultSlideSceneLandscape,
6
6
  args: {
7
7
  title: 'Slide Scene',
@@ -0,0 +1,26 @@
1
+ import { DefaultVideoSceneHorizontal } from './default-video-scene-Horizontal'
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/DefaultVideoSceneHorizontal',
8
+ component: DefaultVideoSceneHorizontal,
9
+ args: {
10
+ title: 'Launch Cut',
11
+ subtitle: 'A portrait video preview with full-bleed media and editorial framing.',
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']) => <DefaultVideoSceneHorizontal {...storyArgs} video={frame} poster={frame} />,
21
+ parameters: { scenePreviewAspectRatio: '9:16' },
22
+ }
23
+ export const Animated = {
24
+ render: (storyArgs: (typeof meta)['args']) => <DefaultVideoSceneHorizontal {...storyArgs} video={frame} poster={frame} />,
25
+ parameters: { scenePreviewAspectRatio: '9:16', scenePreviewMode: 'animation' },
26
+ }
@@ -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: 'DefaultVideoSceneHorizontal',
10
+ description: 'Video preview scene (Portrait 9:16 variant)',
11
+ sourceFile: 'src/core/scene/video/default-video-scene-Horizontal.tsx',
12
+ aspectRatio: '9:16',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
14
+ }
15
+
16
+ export const DefaultVideoSceneHorizontal = VideoComponent(componentMetadata)(function DefaultVideoSceneHorizontal(
17
+ props: ComponentProps<typeof DefaultVideoScene>,
18
+ ) {
19
+ return <DefaultVideoScene {...props} />
20
+ })