@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecuting/video-project-core",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "main": "src/index.ts",
@@ -22,7 +22,7 @@
22
22
  "test": "pnpm exec vitest run"
23
23
  },
24
24
  "dependencies": {
25
- "@vibecuting/component-project-helper": "0.1.27",
25
+ "@vibecuting/component-project-helper": "0.1.29",
26
26
  "@remotion/transitions": "4.0.473",
27
27
  "mermaid": "^11.12.2",
28
28
  "react": "^19.0.0",
@@ -1,6 +1,5 @@
1
1
  import type { CSSProperties, ReactNode } from 'react'
2
2
  import { useId } from 'react'
3
- import { AbsoluteFill } from 'remotion'
4
3
 
5
4
  import { SceneMotionStack } from './use-scene-child-motion'
6
5
  import { useResolvedSceneTheme, useSceneConfig } from './use-scene-config'
@@ -27,7 +26,7 @@ export function BaseRemotionScene({
27
26
  const resolvedTheme = useResolvedSceneTheme()
28
27
 
29
28
  return (
30
- <AbsoluteFill
29
+ <div
31
30
  id={resolvedId}
32
31
  data-scene-name={name}
33
32
  data-scene-display-mode={sceneConfig.sceneType}
@@ -37,11 +36,13 @@ export function BaseRemotionScene({
37
36
  ...(sceneConfig.sceneToken ? {} : (resolvedTheme?.variables ?? {})),
38
37
  overflow: 'hidden',
39
38
  position: 'relative',
39
+ width: '100%',
40
+ height: '100%',
40
41
  isolation: 'isolate',
41
42
  ...style,
42
43
  }}
43
44
  >
44
45
  <SceneMotionStack>{children}</SceneMotionStack>
45
- </AbsoluteFill>
46
+ </div>
46
47
  )
47
48
  }
@@ -3,6 +3,14 @@ import { describe, expect, it } from 'vitest'
3
3
  import {
4
4
  Default2DScene,
5
5
  Default3DScene,
6
+ DefaultLivePhotoScene,
7
+ DefaultEffectsPhotoScene,
8
+ Default3DVRPhotoScene,
9
+ DefaultPhotoStripScene,
10
+ DefaultPhotoWallScene,
11
+ DefaultVideoScene,
12
+ DefaultVideoSplitScene,
13
+ DefaultVideoWallScene,
6
14
  componentMetadata as abstractBackgroundCoverMetadata,
7
15
  componentMetadata as annotationMetadata,
8
16
  componentMetadata as beforeAfterMetadata,
@@ -17,6 +25,18 @@ import {
17
25
  componentMetadata as calloutMetadata,
18
26
  componentMetadata as heroMetadata,
19
27
  componentMetadata as imageHeroMetadata,
28
+ DefaultLivePhotoSceneMetadata,
29
+ DefaultEffectsPhotoSceneMetadata,
30
+ DefaultEffectsPhotoSceneLandscapeMetadata,
31
+ DefaultEffectsPhotoSceneHorizontalMetadata,
32
+ Default3DVRPhotoSceneMetadata,
33
+ Default3DVRPhotoSceneLandscapeMetadata,
34
+ Default3DVRPhotoSceneHorizontalMetadata,
35
+ DefaultPhotoStripSceneMetadata,
36
+ DefaultPhotoWallSceneMetadata,
37
+ DefaultVideoSceneMetadata,
38
+ DefaultVideoSplitSceneMetadata,
39
+ DefaultVideoWallSceneMetadata,
20
40
  componentMetadata as comparisonMetadata,
21
41
  DefaultComparisonSlide,
22
42
  componentMetadata as codeSnippetMetadata,
@@ -104,9 +124,29 @@ describe('video-project-core demo exports', () => {
104
124
  expect(DefaultOutroBumper).toBeTypeOf('function')
105
125
  expect(Default2DScene).toBeTypeOf('function')
106
126
  expect(Default3DScene).toBeTypeOf('function')
127
+ expect(DefaultLivePhotoScene).toBeTypeOf('function')
128
+ expect(DefaultEffectsPhotoScene).toBeTypeOf('function')
129
+ expect(Default3DVRPhotoScene).toBeTypeOf('function')
130
+ expect(DefaultEffectsPhotoSceneMetadata.sceneFamily).toBe('custom')
131
+ expect(DefaultEffectsPhotoSceneLandscapeMetadata.sceneFamily).toBe('custom')
132
+ expect(DefaultEffectsPhotoSceneHorizontalMetadata.sceneFamily).toBe('custom')
133
+ expect(Default3DVRPhotoSceneMetadata.sceneFamily).toBe('custom')
134
+ expect(Default3DVRPhotoSceneLandscapeMetadata.sceneFamily).toBe('custom')
135
+ expect(Default3DVRPhotoSceneHorizontalMetadata.sceneFamily).toBe('custom')
136
+ expect(DefaultPhotoWallScene).toBeTypeOf('function')
137
+ expect(DefaultPhotoStripScene).toBeTypeOf('function')
138
+ expect(DefaultVideoScene).toBeTypeOf('function')
139
+ expect(DefaultVideoSplitScene).toBeTypeOf('function')
140
+ expect(DefaultVideoWallScene).toBeTypeOf('function')
107
141
  expect(DefaultSlideScene).toBeTypeOf('function')
108
142
  expect(heroMetadata.sceneFamily).toBe('slide')
109
143
  expect(imageHeroMetadata.sceneFamily).toBe('slide')
144
+ expect(DefaultLivePhotoSceneMetadata.sceneFamily).toBe('custom')
145
+ expect(DefaultPhotoWallSceneMetadata.sceneFamily).toBe('custom')
146
+ expect(DefaultPhotoStripSceneMetadata.sceneFamily).toBe('custom')
147
+ expect(DefaultVideoSceneMetadata.sceneFamily).toBe('custom')
148
+ expect(DefaultVideoSplitSceneMetadata.sceneFamily).toBe('custom')
149
+ expect(DefaultVideoWallSceneMetadata.sceneFamily).toBe('custom')
110
150
  expect(splitMetadata.sceneFamily).toBe('slide')
111
151
  expect(chapterMetadata.sceneFamily).toBe('slide')
112
152
  expect(abstractBackgroundCoverMetadata.sceneFamily).toBe('slide')
@@ -1,7 +1,7 @@
1
1
  import { Default2DSceneHorizontal } from './default-2d-scene-Horizontal'
2
2
 
3
3
  const meta = {
4
- title: 'core/scene/Default2DSceneHorizontal',
4
+ title: 'core/scene/2dscene/Default2DSceneHorizontal',
5
5
  component: Default2DSceneHorizontal,
6
6
  args: {
7
7
  title: '2D Scene',
@@ -1,7 +1,7 @@
1
1
  import { Default2DSceneLandscape } from './default-2d-scene-Landscape'
2
2
 
3
3
  const meta = {
4
- title: 'core/scene/Default2DSceneLandscape',
4
+ title: 'core/scene/2dscene/Default2DSceneLandscape',
5
5
  component: Default2DSceneLandscape,
6
6
  args: {
7
7
  title: '2D Scene',
@@ -1,7 +1,7 @@
1
1
  import { Default3DSceneHorizontal } from './default-3d-scene-Horizontal'
2
2
 
3
3
  const meta = {
4
- title: 'core/scene/Default3DSceneHorizontal',
4
+ title: 'core/scene/3dscene/Default3DSceneHorizontal',
5
5
  component: Default3DSceneHorizontal,
6
6
  args: {
7
7
  title: '3D Scene',
@@ -1,7 +1,7 @@
1
1
  import { Default3DSceneLandscape } from './default-3d-scene-Landscape'
2
2
 
3
3
  const meta = {
4
- title: 'core/scene/Default3DSceneLandscape',
4
+ title: 'core/scene/3dscene/Default3DSceneLandscape',
5
5
  component: Default3DSceneLandscape,
6
6
  args: {
7
7
  title: '3D Scene',
@@ -1,4 +1,7 @@
1
1
  export * from './2dscene'
2
2
  export * from './3dscene'
3
+ export * from './photo'
4
+ export * from './video'
3
5
  export * from './slide'
4
6
  export * from './utils'
7
+ export { DefaultLivePhotoSceneMetadata, DefaultLivePhotoSceneLandscapeMetadata, DefaultLivePhotoSceneHorizontalMetadata } from './photo'
@@ -0,0 +1,46 @@
1
+ import { Default3DVRPhotoSceneHorizontal } from './default-3dvr-photo-scene-Horizontal'
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/3dvr-photo/Default3DVRPhotoSceneHorizontal',
7
+ component: Default3DVRPhotoSceneHorizontal,
8
+ args: {
9
+ title: 'Stereo Window',
10
+ subtitle: '3D camera controls over a still frame with subtle orbit, pitch and depth.',
11
+ badge: '3D VR Photo',
12
+ imageSrc: photoUrl,
13
+ yaw: 12,
14
+ pitch: 4,
15
+ roll: 0,
16
+ perspective: 980,
17
+ zoom: 1,
18
+ depth: 26,
19
+ focusX: 0.5,
20
+ focusY: 0.45,
21
+ accent: '#020617',
22
+ secondaryAccent: '#7c3aed',
23
+ },
24
+ }
25
+
26
+ export default meta
27
+
28
+ export const Default = {
29
+ parameters: {
30
+ scenePreviewAspectRatio: '9:16',
31
+ },
32
+ }
33
+
34
+ export const Animated = {
35
+ parameters: {
36
+ scenePreviewAspectRatio: '9:16',
37
+ scenePreviewMode: 'animation',
38
+ },
39
+ }
40
+
41
+ export const Both = {
42
+ parameters: {
43
+ scenePreviewAspectRatio: '9:16',
44
+ scenePreviewMode: 'both',
45
+ },
46
+ }
@@ -0,0 +1,20 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+
5
+ import { Default3DVRPhotoScene, componentMetadata as baseComponentMetadata } from './default-3dvr-photo-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'Default3DVRPhotoSceneHorizontal',
10
+ description: '3D VR photo scene for 9:16 portrait previews with a vertical layout',
11
+ sourceFile: 'src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene-Horizontal.tsx',
12
+ aspectRatio: '9:16',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
14
+ }
15
+
16
+ export const Default3DVRPhotoSceneHorizontal = VideoComponent(componentMetadata)(function Default3DVRPhotoSceneHorizontal(
17
+ props: ComponentProps<typeof Default3DVRPhotoScene>,
18
+ ) {
19
+ return <Default3DVRPhotoScene {...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 { Default3DVRPhotoScene, componentMetadata as baseComponentMetadata } from './default-3dvr-photo-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'Default3DVRPhotoSceneLandscape',
10
+ description: '3D VR photo scene for 16:9 landscape previews with a wide cinematic layout',
11
+ sourceFile: 'src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene-Landscape.tsx',
12
+ aspectRatio: '16:9',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
14
+ }
15
+
16
+ export const Default3DVRPhotoSceneLandscape = VideoComponent(componentMetadata)(function Default3DVRPhotoSceneLandscape(
17
+ props: ComponentProps<typeof Default3DVRPhotoScene>,
18
+ ) {
19
+ return <Default3DVRPhotoScene {...props} />
20
+ })
@@ -0,0 +1,46 @@
1
+ import { Default3DVRPhotoSceneLandscape } from './default-3dvr-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/3dvr-photo/Default3DVRPhotoSceneLandscape',
7
+ component: Default3DVRPhotoSceneLandscape,
8
+ args: {
9
+ title: 'Stereo Window',
10
+ subtitle: '3D camera controls over a still frame with subtle orbit, pitch and depth.',
11
+ badge: '3D VR Photo',
12
+ imageSrc: photoUrl,
13
+ yaw: 12,
14
+ pitch: 4,
15
+ roll: 0,
16
+ perspective: 980,
17
+ zoom: 1,
18
+ depth: 26,
19
+ focusX: 0.5,
20
+ focusY: 0.45,
21
+ accent: '#020617',
22
+ secondaryAccent: '#1d4ed8',
23
+ },
24
+ }
25
+
26
+ export default meta
27
+
28
+ export const Default = {
29
+ parameters: {
30
+ scenePreviewAspectRatio: '16:9',
31
+ },
32
+ }
33
+
34
+ export const Animated = {
35
+ parameters: {
36
+ scenePreviewAspectRatio: '16:9',
37
+ scenePreviewMode: 'animation',
38
+ },
39
+ }
40
+
41
+ export const Both = {
42
+ parameters: {
43
+ scenePreviewAspectRatio: '16:9',
44
+ scenePreviewMode: 'both',
45
+ },
46
+ }
@@ -0,0 +1,255 @@
1
+ import { z } from 'zod'
2
+
3
+ import { VideoComponent, defineScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+ import { Img, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion'
5
+
6
+ import { BaseRemotionScene, type BaseRemotionSceneProps, useRemotionSceneRuntime, useSceneConfig } from '../../../../base'
7
+ import { defineSceneComponent } from '../../../../plugins/scene-component'
8
+
9
+ export interface Default3DVRPhotoSceneProps extends BaseRemotionSceneProps {
10
+ sceneId?: string
11
+ sceneName?: string
12
+ title?: string
13
+ subtitle?: string
14
+ badge?: string
15
+ imageSrc: string
16
+ yaw?: number
17
+ pitch?: number
18
+ roll?: number
19
+ perspective?: number
20
+ zoom?: number
21
+ depth?: number
22
+ focusX?: number
23
+ focusY?: number
24
+ accent?: string
25
+ secondaryAccent?: string
26
+ textColor?: string
27
+ }
28
+
29
+ export const componentMetadata = defineScenePluginMetadata({
30
+ resourceKind: 'scene',
31
+ name: 'Default3DVRPhotoScene',
32
+ description: 'Photo scene with 3D VR camera controls and perspective motion',
33
+ sourceFile: 'src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene.tsx',
34
+ pluginKey: 'core.photo.3dvr-photo',
35
+ tags: ['scene', 'photo', '3d', 'vr', 'perspective'],
36
+ aspectRatio: '16:9',
37
+ sceneType: 'scene',
38
+ sceneFamily: 'custom',
39
+ rootLayout: 'absolute-fill',
40
+ propsTypeName: 'Default3DVRPhotoSceneProps',
41
+ })
42
+
43
+ export const Default3DVRPhotoScene = VideoComponent(componentMetadata)(
44
+ defineSceneComponent({
45
+ family: 'custom',
46
+ propsSchema: z.object({
47
+ sceneId: z.string().optional(),
48
+ sceneName: z.string().optional(),
49
+ title: z.string().optional(),
50
+ subtitle: z.string().optional(),
51
+ badge: z.string().optional(),
52
+ imageSrc: z.string(),
53
+ yaw: z.number().optional(),
54
+ pitch: z.number().optional(),
55
+ roll: z.number().optional(),
56
+ perspective: z.number().optional(),
57
+ zoom: z.number().optional(),
58
+ depth: z.number().optional(),
59
+ focusX: z.number().min(0).max(1).optional(),
60
+ focusY: z.number().min(0).max(1).optional(),
61
+ accent: z.string().optional(),
62
+ secondaryAccent: z.string().optional(),
63
+ textColor: z.string().optional(),
64
+ }),
65
+ component: function Default3DVRPhotoScene({
66
+ id,
67
+ name,
68
+ sceneId,
69
+ sceneName,
70
+ title,
71
+ subtitle,
72
+ badge,
73
+ imageSrc,
74
+ yaw = 12,
75
+ pitch = 4,
76
+ roll = 0,
77
+ perspective = 980,
78
+ zoom = 1,
79
+ depth = 24,
80
+ focusX = 0.5,
81
+ focusY = 0.5,
82
+ accent,
83
+ secondaryAccent,
84
+ textColor,
85
+ }: Default3DVRPhotoSceneProps) {
86
+ const runtime = useRemotionSceneRuntime()
87
+ const sceneConfig = useSceneConfig()
88
+ const frame = useCurrentFrame()
89
+ const { fps } = useVideoConfig()
90
+ const portrait = runtime.height > runtime.width
91
+ const animated = sceneConfig.sceneType === 'animation'
92
+ const entrance = sceneConfig.sceneType === 'animation' ? spring({ frame, fps, delay: 4, config: { damping: 18, mass: 0.8, stiffness: 120 } }) : 1
93
+ const orbit = animated ? Math.sin((frame / fps) * Math.PI * 0.55) : 0
94
+ const cameraYaw = yaw + orbit * 4 + (1 - entrance) * (portrait ? -3 : -4)
95
+ const cameraPitch = pitch + Math.sin((frame / fps) * Math.PI * 0.35) * 1.5 + (1 - entrance) * 1.6
96
+ const cameraRoll = roll + Math.sin((frame / fps) * Math.PI * 0.25) * 0.45
97
+ const cameraZoom = zoom + entrance * 0.06 + (1 - entrance) * 0.02
98
+ const titleSize = Math.round(runtime.height * (portrait ? 0.034 : 0.04))
99
+ const subtitleSize = Math.round(runtime.height * 0.017)
100
+ const focusOffsetX = interpolate(focusX, [0, 1], [-12, 12])
101
+ const focusOffsetY = interpolate(focusY, [0, 1], [12, -12])
102
+ const imageFilter = animated ? 'brightness(1.08) contrast(1.04) saturate(1.08)' : 'brightness(2.05) contrast(1.22) saturate(1.3)'
103
+ const framePaddingX = Math.round(runtime.width * (portrait ? 0.025 : 0.02))
104
+ const framePaddingY = Math.round(runtime.height * (portrait ? 0.02 : 0.018))
105
+ const mainFrameLeft = framePaddingX
106
+ const mainFrameTop = framePaddingY
107
+ const mainFrameWidth = Math.max(1, runtime.width - framePaddingX * 2)
108
+ const mainFrameHeight = Math.max(1, runtime.height - framePaddingY * 2)
109
+ const mainFrameShadow = portrait
110
+ ? '0 26px 90px rgba(0,0,0,0.38)'
111
+ : '0 34px 110px rgba(0,0,0,0.42)'
112
+ const imageTransform = animated
113
+ ? `translate3d(${focusOffsetX}px, ${focusOffsetY}px, ${depth}px) rotateX(${cameraPitch}deg) rotateY(${cameraYaw}deg) rotateZ(${cameraRoll}deg) scale(${cameraZoom})`
114
+ : `translate3d(${focusOffsetX * 0.4}px, ${focusOffsetY * 0.35}px, ${depth * 0.45}px) rotateX(${pitch * 0.15}deg) rotateY(${yaw * 0.12}deg) rotateZ(${roll * 0.08}deg) scale(${1.01 + (zoom - 1) * 0.4})`
115
+
116
+ return (
117
+ <BaseRemotionScene
118
+ id={id ?? sceneId}
119
+ name={name ?? sceneName ?? title}
120
+ style={{
121
+ background:
122
+ portrait
123
+ ? `linear-gradient(180deg, ${accent ?? '#020617'} 0%, #000000 100%)`
124
+ : `radial-gradient(circle at 50% 20%, ${secondaryAccent ?? 'rgba(255,255,255,0.08)'} 0%, ${accent ?? '#020617'} 52%, #000000 100%)`,
125
+ color: textColor ?? '#f8fafc',
126
+ padding: Math.round(runtime.height * (portrait ? 0.02 : 0.03)),
127
+ }}
128
+ >
129
+ <div
130
+ style={{
131
+ position: 'absolute',
132
+ inset: 0,
133
+ overflow: 'hidden',
134
+ border: '1px solid rgba(255,255,255,0.12)',
135
+ background: 'rgba(2,6,23,0.84)',
136
+ perspective: `${perspective}px`,
137
+ transformStyle: 'preserve-3d',
138
+ isolation: 'isolate',
139
+ }}
140
+ >
141
+ <div
142
+ aria-hidden="true"
143
+ style={{
144
+ position: 'absolute',
145
+ top: Math.max(0, mainFrameTop - 18),
146
+ left: Math.max(0, mainFrameLeft - 18),
147
+ width: Math.max(1, mainFrameWidth + 36),
148
+ height: Math.max(1, mainFrameHeight + 36),
149
+ opacity: animated ? 0.2 + entrance * 0.08 : 0.04,
150
+ filter: animated ? 'blur(18px) saturate(1.12) brightness(0.95)' : 'blur(10px) saturate(1.05) brightness(1.05)',
151
+ transform: `translate3d(${focusOffsetX * 0.3}px, ${focusOffsetY * 0.22}px, -20px) scale(${1.05 + entrance * 0.04})`,
152
+ }}
153
+ >
154
+ <Img
155
+ src={imageSrc}
156
+ alt={title ?? '3D VR photo'}
157
+ style={{
158
+ width: '100%',
159
+ height: '100%',
160
+ objectFit: 'cover',
161
+ }}
162
+ />
163
+ </div>
164
+
165
+ <div
166
+ style={{
167
+ position: 'absolute',
168
+ top: mainFrameTop,
169
+ left: mainFrameLeft,
170
+ width: mainFrameWidth,
171
+ height: mainFrameHeight,
172
+ transformStyle: 'preserve-3d',
173
+ transform: imageTransform,
174
+ border: animated ? '1px solid rgba(255,255,255,0.12)' : '1px solid rgba(255,255,255,0.2)',
175
+ backgroundColor: animated ? 'rgba(2,6,23,0.2)' : 'rgba(248,250,252,0.08)',
176
+ backgroundImage: `url(${imageSrc})`,
177
+ backgroundSize: 'cover',
178
+ backgroundPosition: `${Math.round(focusX * 100)}% ${Math.round((1 - focusY) * 100)}%`,
179
+ backgroundRepeat: 'no-repeat',
180
+ boxShadow: animated ? '0 22px 70px rgba(0,0,0,0.32)' : '0 14px 42px rgba(0,0,0,0.2)',
181
+ overflow: 'hidden',
182
+ borderRadius: 0,
183
+ filter: imageFilter,
184
+ }}
185
+ >
186
+ </div>
187
+
188
+ <div
189
+ aria-hidden="true"
190
+ style={{
191
+ position: 'absolute',
192
+ inset: 0,
193
+ background:
194
+ animated
195
+ ? 'radial-gradient(circle at center, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0.01) 26%, rgba(2,6,23,0.14) 52%, rgba(2,6,23,0.46) 100%)'
196
+ : 'radial-gradient(circle at center, rgba(255,255,255,0.00) 0%, rgba(255,255,255,0.00) 44%, rgba(2,6,23,0.01) 76%, rgba(2,6,23,0.04) 100%)',
197
+ pointerEvents: 'none',
198
+ }}
199
+ />
200
+
201
+ <div
202
+ aria-hidden="true"
203
+ style={{
204
+ position: 'absolute',
205
+ top: mainFrameTop,
206
+ left: mainFrameLeft,
207
+ width: mainFrameWidth,
208
+ height: mainFrameHeight,
209
+ border: '1px solid rgba(255,255,255,0.18)',
210
+ pointerEvents: 'none',
211
+ }}
212
+ />
213
+
214
+ <div
215
+ style={{
216
+ position: 'absolute',
217
+ inset: 0,
218
+ display: 'grid',
219
+ alignContent: 'space-between',
220
+ padding: Math.round(runtime.height * (portrait ? 0.02 : 0.03)),
221
+ zIndex: 2,
222
+ pointerEvents: 'none',
223
+ }}
224
+ >
225
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 12 }}>
226
+ <div
227
+ style={{
228
+ display: 'inline-flex',
229
+ alignItems: 'center',
230
+ gap: 8,
231
+ border: '1px solid rgba(255,255,255,0.18)',
232
+ background: 'rgba(2,6,23,0.6)',
233
+ color: '#f8fafc',
234
+ padding: '8px 12px',
235
+ letterSpacing: '0.22em',
236
+ textTransform: 'uppercase',
237
+ fontSize: 11,
238
+ lineHeight: 1,
239
+ }}
240
+ >
241
+ {badge ?? '3D VR Photo'}
242
+ </div>
243
+ </div>
244
+
245
+ <div style={{ display: 'grid', gap: 8, maxWidth: portrait ? '86%' : '58%' }}>
246
+ {title ? <div style={{ fontSize: titleSize, fontWeight: 800, letterSpacing: '-0.06em', lineHeight: 0.94 }}>{title}</div> : null}
247
+ {subtitle ? <div style={{ fontSize: subtitleSize, lineHeight: 1.45, opacity: 0.84 }}>{subtitle}</div> : null}
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </BaseRemotionScene>
252
+ )
253
+ },
254
+ }),
255
+ )
@@ -0,0 +1,3 @@
1
+ export { Default3DVRPhotoScene, componentMetadata as Default3DVRPhotoSceneMetadata } from './default-3dvr-photo-scene'
2
+ export { Default3DVRPhotoSceneLandscape, componentMetadata as Default3DVRPhotoSceneLandscapeMetadata } from './default-3dvr-photo-scene-Landscape'
3
+ export { Default3DVRPhotoSceneHorizontal, componentMetadata as Default3DVRPhotoSceneHorizontalMetadata } from './default-3dvr-photo-scene-Horizontal'
@@ -0,0 +1,33 @@
1
+ import { DefaultLivePhotoSceneHorizontal } from './default-live-photo-scene-Horizontal'
2
+
3
+ const photoUrl = 'https://heyaai-statics-1342239856.cos.ap-guangzhou.myqcloud.com/tmp/premium_photo-1779404552087-0f905162376f-121.jpeg'
4
+ const photo = <img src={photoUrl} alt="Night Street" />
5
+
6
+ const meta = {
7
+ title: 'core/scene/photo/DefaultLivePhotoSceneHorizontal',
8
+ component: DefaultLivePhotoSceneHorizontal,
9
+ args: {
10
+ title: 'Night Street',
11
+ subtitle: 'Portrait live-photo treatment with a tighter frame and the same subtle motion language.',
12
+ badge: 'Live Photo',
13
+ accent: '#020617',
14
+ secondaryAccent: '#7c3aed',
15
+ },
16
+ }
17
+
18
+ export default meta
19
+
20
+ export const Default = {
21
+ render: (storyArgs: (typeof meta)['args']) => <DefaultLivePhotoSceneHorizontal {...storyArgs} image={photo} />,
22
+ parameters: {
23
+ scenePreviewAspectRatio: '9:16',
24
+ },
25
+ }
26
+
27
+ export const Animated = {
28
+ render: (storyArgs: (typeof meta)['args']) => <DefaultLivePhotoSceneHorizontal {...storyArgs} image={photo} />,
29
+ parameters: {
30
+ scenePreviewAspectRatio: '9:16',
31
+ scenePreviewMode: 'animation',
32
+ },
33
+ }
@@ -0,0 +1,20 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
4
+
5
+ import { DefaultLivePhotoScene, componentMetadata as baseComponentMetadata } from './default-live-photo-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultLivePhotoSceneHorizontal',
10
+ description: 'Apple-style live photo scene for 9:16 portrait previews with a tighter vertical crop',
11
+ sourceFile: 'src/core/scene/photo/default-live-photo-scene-Horizontal.tsx',
12
+ aspectRatio: '9:16',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
14
+ }
15
+
16
+ export const DefaultLivePhotoSceneHorizontal = VideoComponent(componentMetadata)(function DefaultLivePhotoSceneHorizontal(
17
+ props: ComponentProps<typeof DefaultLivePhotoScene>,
18
+ ) {
19
+ return <DefaultLivePhotoScene {...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 { DefaultLivePhotoScene, componentMetadata as baseComponentMetadata } from './default-live-photo-scene'
6
+
7
+ export const componentMetadata: ScenePluginMetadata = {
8
+ ...baseComponentMetadata,
9
+ name: 'DefaultLivePhotoSceneLandscape',
10
+ description: 'Apple-style live photo scene for 16:9 landscape previews with a wider cinematic crop',
11
+ sourceFile: 'src/core/scene/photo/default-live-photo-scene-Landscape.tsx',
12
+ aspectRatio: '16:9',
13
+ tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
14
+ }
15
+
16
+ export const DefaultLivePhotoSceneLandscape = VideoComponent(componentMetadata)(function DefaultLivePhotoSceneLandscape(
17
+ props: ComponentProps<typeof DefaultLivePhotoScene>,
18
+ ) {
19
+ return <DefaultLivePhotoScene {...props} />
20
+ })
@@ -0,0 +1,33 @@
1
+ import { DefaultLivePhotoSceneLandscape } from './default-live-photo-scene-Landscape'
2
+
3
+ const photoUrl = 'https://heyaai-statics-1342239856.cos.ap-guangzhou.myqcloud.com/tmp/premium_photo-1779404552087-0f905162376f-121.jpeg'
4
+ const photo = <img src={photoUrl} alt="Summer Harbor" />
5
+
6
+ const meta = {
7
+ title: 'core/scene/photo/DefaultLivePhotoSceneLandscape',
8
+ component: DefaultLivePhotoSceneLandscape,
9
+ args: {
10
+ title: 'Summer Harbor',
11
+ subtitle: 'A single image scene with an Apple-style live push-in and luminous framing.',
12
+ badge: 'Live Photo',
13
+ accent: '#020617',
14
+ secondaryAccent: '#1d4ed8',
15
+ },
16
+ }
17
+
18
+ export default meta
19
+
20
+ export const Default = {
21
+ render: (storyArgs: (typeof meta)['args']) => <DefaultLivePhotoSceneLandscape {...storyArgs} image={photo} />,
22
+ parameters: {
23
+ scenePreviewAspectRatio: '16:9',
24
+ },
25
+ }
26
+
27
+ export const Animated = {
28
+ render: (storyArgs: (typeof meta)['args']) => <DefaultLivePhotoSceneLandscape {...storyArgs} image={photo} />,
29
+ parameters: {
30
+ scenePreviewAspectRatio: '16:9',
31
+ scenePreviewMode: 'animation',
32
+ },
33
+ }