@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.
- package/package.json +2 -2
- package/src/base/BaseRemotionScene.tsx +4 -3
- package/src/core/index.test.ts +40 -0
- package/src/core/scene/2dscene/default-2d-scene-Horizontal.stories.tsx +1 -1
- package/src/core/scene/2dscene/default-2d-scene.stories.tsx +1 -1
- package/src/core/scene/3dscene/default-3d-scene-Horizontal.stories.tsx +1 -1
- package/src/core/scene/3dscene/default-3d-scene.stories.tsx +1 -1
- package/src/core/scene/index.ts +3 -0
- package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene-Horizontal.stories.tsx +46 -0
- package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene-Horizontal.tsx +20 -0
- package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene-Landscape.tsx +20 -0
- package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene.stories.tsx +46 -0
- package/src/core/scene/photo/3dvr-photo/default-3dvr-photo-scene.tsx +255 -0
- package/src/core/scene/photo/3dvr-photo/index.ts +3 -0
- package/src/core/scene/photo/default-live-photo-scene-Horizontal.stories.tsx +33 -0
- package/src/core/scene/photo/default-live-photo-scene-Horizontal.tsx +20 -0
- package/src/core/scene/photo/default-live-photo-scene-Landscape.tsx +20 -0
- package/src/core/scene/photo/default-live-photo-scene.stories.tsx +33 -0
- package/src/core/scene/photo/default-live-photo-scene.tsx +432 -0
- package/src/core/scene/photo/default-photo-strip-scene-Horizontal.tsx +20 -0
- package/src/core/scene/photo/default-photo-strip-scene-Landscape.tsx +20 -0
- package/src/core/scene/photo/default-photo-strip-scene.stories.tsx +38 -0
- package/src/core/scene/photo/default-photo-strip-scene.tsx +19 -0
- package/src/core/scene/photo/default-photo-wall-scene-Horizontal.tsx +20 -0
- package/src/core/scene/photo/default-photo-wall-scene-Landscape.tsx +20 -0
- package/src/core/scene/photo/default-photo-wall-scene.stories.tsx +38 -0
- package/src/core/scene/photo/default-photo-wall-scene.tsx +19 -0
- package/src/core/scene/photo/effects-photo/default-effects-photo-scene-Horizontal.stories.tsx +44 -0
- package/src/core/scene/photo/effects-photo/default-effects-photo-scene-Horizontal.tsx +20 -0
- package/src/core/scene/photo/effects-photo/default-effects-photo-scene-Landscape.tsx +20 -0
- package/src/core/scene/photo/effects-photo/default-effects-photo-scene.stories.tsx +44 -0
- package/src/core/scene/photo/effects-photo/default-effects-photo-scene.tsx +352 -0
- package/src/core/scene/photo/effects-photo/index.ts +3 -0
- package/src/core/scene/photo/index.ts +25 -0
- package/src/core/scene/photo/photo-storybook.test.tsx +112 -0
- package/src/core/scene/slide/default-slide-scene-Horizontal.animated.stories.tsx +1 -1
- package/src/core/scene/slide/default-slide-scene-Horizontal.stories.tsx +1 -1
- package/src/core/scene/slide/default-slide-scene.animated.stories.tsx +1 -1
- package/src/core/scene/slide/default-slide-scene.stories.tsx +1 -1
- package/src/core/scene/video/default-video-scene-Horizontal.stories.tsx +26 -0
- package/src/core/scene/video/default-video-scene-Horizontal.tsx +20 -0
- package/src/core/scene/video/default-video-scene-Landscape.tsx +20 -0
- package/src/core/scene/video/default-video-scene.stories.tsx +26 -0
- package/src/core/scene/video/default-video-scene.tsx +283 -0
- package/src/core/scene/video/default-video-split-scene-Horizontal.tsx +20 -0
- package/src/core/scene/video/default-video-split-scene-Landscape.tsx +20 -0
- package/src/core/scene/video/default-video-split-scene.stories.tsx +41 -0
- package/src/core/scene/video/default-video-split-scene.tsx +19 -0
- package/src/core/scene/video/default-video-wall-scene-Horizontal.tsx +20 -0
- package/src/core/scene/video/default-video-wall-scene-Landscape.tsx +20 -0
- package/src/core/scene/video/default-video-wall-scene.stories.tsx +40 -0
- package/src/core/scene/video/default-video-wall-scene.tsx +19 -0
- package/src/core/scene/video/index.ts +9 -0
- package/src/storybook/remotion-preview.tsx +112 -6
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { cloneElement, isValidElement } from 'react'
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
import { VideoComponent, defineScenePluginMetadata } from '@vibecuting/component-project-helper'
|
|
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 DefaultLivePhotoSceneProps extends BaseRemotionSceneProps {
|
|
13
|
+
sceneId?: string
|
|
14
|
+
sceneName?: string
|
|
15
|
+
title?: string
|
|
16
|
+
subtitle?: string
|
|
17
|
+
badge?: string
|
|
18
|
+
image: ReactNode
|
|
19
|
+
images?: ReactNode[]
|
|
20
|
+
variant?: 'live' | 'wall' | 'strip'
|
|
21
|
+
accent?: string
|
|
22
|
+
secondaryAccent?: string
|
|
23
|
+
textColor?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const componentMetadata = defineScenePluginMetadata({
|
|
27
|
+
resourceKind: 'scene',
|
|
28
|
+
name: 'DefaultLivePhotoScene',
|
|
29
|
+
description: 'Apple-style live photo scene with a subtle push-in and luminous overlay',
|
|
30
|
+
sourceFile: 'src/core/scene/photo/default-live-photo-scene.tsx',
|
|
31
|
+
pluginKey: 'core.photo.live-photo',
|
|
32
|
+
tags: ['scene', 'photo', 'live-photo', 'image', 'apple'],
|
|
33
|
+
aspectRatio: '16:9',
|
|
34
|
+
sceneType: 'scene',
|
|
35
|
+
sceneFamily: 'custom',
|
|
36
|
+
rootLayout: 'absolute-fill',
|
|
37
|
+
propsTypeName: 'DefaultLivePhotoSceneProps',
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
function LivePhotoBadge({ label }: { label: string }) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
style={{
|
|
44
|
+
display: 'inline-flex',
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
gap: 8,
|
|
47
|
+
border: '1px solid rgba(255,255,255,0.18)',
|
|
48
|
+
background: 'rgba(2,6,23,0.6)',
|
|
49
|
+
color: '#f8fafc',
|
|
50
|
+
padding: '8px 12px',
|
|
51
|
+
letterSpacing: '0.22em',
|
|
52
|
+
textTransform: 'uppercase',
|
|
53
|
+
fontSize: 11,
|
|
54
|
+
lineHeight: 1,
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<span
|
|
58
|
+
aria-hidden="true"
|
|
59
|
+
style={{
|
|
60
|
+
width: 8,
|
|
61
|
+
height: 8,
|
|
62
|
+
borderRadius: 9999,
|
|
63
|
+
background: 'rgba(239,68,68,0.95)',
|
|
64
|
+
boxShadow: '0 0 0 4px rgba(239,68,68,0.14)',
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
{label}
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderCoverMedia(node: ReactNode) {
|
|
73
|
+
if (isValidElement(node)) {
|
|
74
|
+
const existingStyle = (node.props as { style?: Record<string, unknown> }).style ?? {}
|
|
75
|
+
return cloneElement(node, {
|
|
76
|
+
...(node.props as Record<string, unknown>),
|
|
77
|
+
style: {
|
|
78
|
+
...existingStyle,
|
|
79
|
+
width: '100%',
|
|
80
|
+
height: '100%',
|
|
81
|
+
objectFit: 'cover',
|
|
82
|
+
display: 'block',
|
|
83
|
+
minWidth: 0,
|
|
84
|
+
minHeight: 0,
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return <div style={{ width: '100%', height: '100%', minWidth: 0, minHeight: 0 }}>{node}</div>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const DefaultLivePhotoScene = VideoComponent(componentMetadata)(
|
|
93
|
+
defineSceneComponent({
|
|
94
|
+
family: 'custom',
|
|
95
|
+
propsSchema: z.object({
|
|
96
|
+
sceneId: z.string().optional(),
|
|
97
|
+
sceneName: z.string().optional(),
|
|
98
|
+
title: z.string().optional(),
|
|
99
|
+
subtitle: z.string().optional(),
|
|
100
|
+
badge: z.string().optional(),
|
|
101
|
+
image: z.any(),
|
|
102
|
+
images: z.array(z.any()).optional(),
|
|
103
|
+
variant: z.enum(['live', 'wall', 'strip']).optional(),
|
|
104
|
+
accent: z.string().optional(),
|
|
105
|
+
secondaryAccent: z.string().optional(),
|
|
106
|
+
textColor: z.string().optional(),
|
|
107
|
+
}),
|
|
108
|
+
component: function DefaultLivePhotoScene({
|
|
109
|
+
id,
|
|
110
|
+
name,
|
|
111
|
+
sceneId,
|
|
112
|
+
sceneName,
|
|
113
|
+
title,
|
|
114
|
+
subtitle,
|
|
115
|
+
badge,
|
|
116
|
+
image,
|
|
117
|
+
images,
|
|
118
|
+
variant = 'live',
|
|
119
|
+
accent,
|
|
120
|
+
secondaryAccent,
|
|
121
|
+
textColor,
|
|
122
|
+
}: DefaultLivePhotoSceneProps) {
|
|
123
|
+
const runtime = useRemotionSceneRuntime()
|
|
124
|
+
const sceneConfig = useSceneConfig()
|
|
125
|
+
const frame = useCurrentFrame()
|
|
126
|
+
const { fps } = useVideoConfig()
|
|
127
|
+
const portrait = runtime.height > runtime.width
|
|
128
|
+
const animationProgress =
|
|
129
|
+
sceneConfig.sceneType === 'animation'
|
|
130
|
+
? spring({
|
|
131
|
+
frame,
|
|
132
|
+
fps,
|
|
133
|
+
config: {
|
|
134
|
+
damping: 18,
|
|
135
|
+
mass: 0.8,
|
|
136
|
+
stiffness: 110,
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
: 1
|
|
140
|
+
const titleSize = Math.round(runtime.height * (portrait ? 0.034 : 0.04))
|
|
141
|
+
const subtitleSize = Math.round(runtime.height * 0.017)
|
|
142
|
+
const padding = Math.round(runtime.height * (portrait ? 0.012 : 0.014))
|
|
143
|
+
const liveMediaHeight = Math.max(240, Math.floor(runtime.height * (portrait ? 0.84 : 0.86)))
|
|
144
|
+
const revealFrame = 12
|
|
145
|
+
const liveBreath = sceneConfig.sceneType === 'animation' ? Math.sin((frame / fps) * Math.PI * 0.85) * 0.012 : 0
|
|
146
|
+
const liveFocus = sceneConfig.sceneType === 'animation' ? 1 - animationProgress : 0
|
|
147
|
+
const revealEase = sceneConfig.sceneType === 'animation' ? spring({ frame: Math.max(0, frame - revealFrame), fps, config: { damping: 22, mass: 0.7, stiffness: 170 } }) : 1
|
|
148
|
+
const imageScale = sceneConfig.sceneType === 'animation' ? 1.06 + revealEase * 0.24 + liveBreath : 1
|
|
149
|
+
const imageShiftY = sceneConfig.sceneType === 'animation' ? revealEase * 32 + liveBreath * 72 : 0
|
|
150
|
+
const imageShiftX = sceneConfig.sceneType === 'animation' ? (portrait ? -10 : -18) * revealEase + liveBreath * (portrait ? 18 : 28) : 0
|
|
151
|
+
const backgroundScale = sceneConfig.sceneType === 'animation' ? 1.28 + revealEase * 0.12 : 1.16
|
|
152
|
+
const spotlightOpacity = sceneConfig.sceneType === 'animation' ? 0.16 + revealEase * 0.12 : 0.08
|
|
153
|
+
const vignetteOpacity = sceneConfig.sceneType === 'animation' ? 0.42 + revealEase * 0.18 : 0.28
|
|
154
|
+
const imageSet = (images?.length ? images : [image]).slice(0, 4)
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<BaseRemotionScene
|
|
158
|
+
id={id ?? sceneId}
|
|
159
|
+
name={name ?? sceneName ?? title}
|
|
160
|
+
style={{
|
|
161
|
+
background:
|
|
162
|
+
portrait
|
|
163
|
+
? `linear-gradient(180deg, ${accent ?? '#050505'} 0%, #020617 45%, #000000 100%)`
|
|
164
|
+
: `radial-gradient(circle at 50% 28%, ${secondaryAccent ?? 'rgba(255,255,255,0.08)'} 0%, ${accent ?? '#020617'} 48%, #000000 100%)`,
|
|
165
|
+
color: textColor ?? '#f8fafc',
|
|
166
|
+
padding,
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<section
|
|
170
|
+
style={{
|
|
171
|
+
width: '100%',
|
|
172
|
+
height: '100%',
|
|
173
|
+
display: 'flex',
|
|
174
|
+
flexDirection: 'column',
|
|
175
|
+
gap: portrait ? 14 : 18,
|
|
176
|
+
minWidth: 0,
|
|
177
|
+
minHeight: 0,
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
{variant === 'wall' ? (
|
|
181
|
+
<div
|
|
182
|
+
style={{
|
|
183
|
+
position: 'relative',
|
|
184
|
+
width: '100%',
|
|
185
|
+
height: '100%',
|
|
186
|
+
aspectRatio: portrait ? '9 / 16' : '16 / 9',
|
|
187
|
+
minWidth: 0,
|
|
188
|
+
minHeight: 0,
|
|
189
|
+
overflow: 'hidden',
|
|
190
|
+
border: '1px solid rgba(255,255,255,0.1)',
|
|
191
|
+
background: 'rgba(2,6,23,0.84)',
|
|
192
|
+
display: 'grid',
|
|
193
|
+
gridTemplateColumns: portrait ? '1fr 1fr' : '1.2fr 0.8fr',
|
|
194
|
+
gridTemplateRows: portrait ? '1fr 1fr' : '1fr 1fr',
|
|
195
|
+
gap: 6,
|
|
196
|
+
padding,
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
{imageSet.map((item, index) => (
|
|
200
|
+
<SceneMotionLayer key={`wall-${index}`} variant="media" delayIndex={index}>
|
|
201
|
+
<div
|
|
202
|
+
style={{
|
|
203
|
+
width: '100%',
|
|
204
|
+
height: '100%',
|
|
205
|
+
minHeight: 0,
|
|
206
|
+
overflow: 'hidden',
|
|
207
|
+
border: '1px solid rgba(255,255,255,0.06)',
|
|
208
|
+
background: 'rgba(15,23,42,0.88)',
|
|
209
|
+
transform: `scale(${1 + (1 - animationProgress) * 0.018})`,
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{renderCoverMedia(item)}
|
|
213
|
+
</div>
|
|
214
|
+
</SceneMotionLayer>
|
|
215
|
+
))}
|
|
216
|
+
<div style={{ position: 'absolute', inset: padding, display: 'flex', justifyContent: 'space-between', alignItems: 'start', pointerEvents: 'none' }}>
|
|
217
|
+
<LivePhotoBadge label={badge ?? 'Photo Wall'} />
|
|
218
|
+
<div style={{ fontSize: Math.round(runtime.height * 0.013), letterSpacing: '0.24em', textTransform: 'uppercase', opacity: 0.58 }}>
|
|
219
|
+
{imageSet.length} frames
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
) : variant === 'strip' ? (
|
|
224
|
+
<div
|
|
225
|
+
style={{
|
|
226
|
+
position: 'relative',
|
|
227
|
+
minWidth: 0,
|
|
228
|
+
minHeight: 0,
|
|
229
|
+
overflow: 'hidden',
|
|
230
|
+
border: '1px solid rgba(255,255,255,0.14)',
|
|
231
|
+
background: 'rgba(2,6,23,0.84)',
|
|
232
|
+
display: 'grid',
|
|
233
|
+
gridTemplateRows: 'auto 1fr auto',
|
|
234
|
+
gap: 12,
|
|
235
|
+
padding,
|
|
236
|
+
flex: 1,
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
|
|
240
|
+
<LivePhotoBadge label={badge ?? 'Photo Strip'} />
|
|
241
|
+
<div style={{ fontSize: Math.round(runtime.height * 0.013), letterSpacing: '0.2em', textTransform: 'uppercase', opacity: 0.56 }}>
|
|
242
|
+
Live sequence
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
<div
|
|
246
|
+
style={{
|
|
247
|
+
display: 'grid',
|
|
248
|
+
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
|
|
249
|
+
gap: 6,
|
|
250
|
+
minWidth: 0,
|
|
251
|
+
minHeight: 0,
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
{imageSet.map((item, index) => (
|
|
255
|
+
<SceneMotionLayer key={`strip-${index}`} variant="media" delayIndex={index}>
|
|
256
|
+
<div
|
|
257
|
+
style={{
|
|
258
|
+
width: '100%',
|
|
259
|
+
height: '100%',
|
|
260
|
+
minHeight: 0,
|
|
261
|
+
overflow: 'hidden',
|
|
262
|
+
border: '1px solid rgba(255,255,255,0.06)',
|
|
263
|
+
background: 'rgba(15,23,42,0.88)',
|
|
264
|
+
transform: `translate3d(0, ${(1 - animationProgress) * 8}px, 0) scale(${1 + (1 - animationProgress) * 0.02})`,
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
{renderCoverMedia(item)}
|
|
268
|
+
</div>
|
|
269
|
+
</SceneMotionLayer>
|
|
270
|
+
))}
|
|
271
|
+
</div>
|
|
272
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
273
|
+
{title ? (
|
|
274
|
+
<SceneMotionLayer variant="hero-title">
|
|
275
|
+
<div style={{ fontSize: titleSize * 0.82, fontWeight: 800, letterSpacing: '-0.06em', lineHeight: 0.95 }}>{title}</div>
|
|
276
|
+
</SceneMotionLayer>
|
|
277
|
+
) : null}
|
|
278
|
+
{subtitle ? <div style={{ fontSize: subtitleSize, lineHeight: 1.45, opacity: 0.84 }}>{subtitle}</div> : null}
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
) : (
|
|
282
|
+
<div
|
|
283
|
+
style={{
|
|
284
|
+
position: 'relative',
|
|
285
|
+
width: '100%',
|
|
286
|
+
height: liveMediaHeight,
|
|
287
|
+
minHeight: liveMediaHeight,
|
|
288
|
+
minWidth: 0,
|
|
289
|
+
overflow: 'hidden',
|
|
290
|
+
border: '1px solid rgba(255,255,255,0.1)',
|
|
291
|
+
background: 'rgba(2,6,23,0.84)',
|
|
292
|
+
flex: 1,
|
|
293
|
+
}}
|
|
294
|
+
>
|
|
295
|
+
<SceneMotionLayer
|
|
296
|
+
variant="media"
|
|
297
|
+
style={{
|
|
298
|
+
position: 'absolute',
|
|
299
|
+
inset: '-10%',
|
|
300
|
+
opacity: sceneConfig.sceneType === 'animation' ? 0.48 : 0.2,
|
|
301
|
+
filter: 'blur(32px) saturate(1.16) brightness(0.92)',
|
|
302
|
+
transform: `translate3d(${imageShiftX * 0.28}px, ${imageShiftY * 0.18}px, 0) scale(${backgroundScale})`,
|
|
303
|
+
}}
|
|
304
|
+
>
|
|
305
|
+
<div
|
|
306
|
+
style={{
|
|
307
|
+
width: '100%',
|
|
308
|
+
height: '100%',
|
|
309
|
+
transform: `translate3d(${imageShiftX * 0.4}px, ${imageShiftY * 0.32}px, 0) scale(${imageScale + 0.14})`,
|
|
310
|
+
transformOrigin: 'center center',
|
|
311
|
+
}}
|
|
312
|
+
>
|
|
313
|
+
{renderCoverMedia(image)}
|
|
314
|
+
</div>
|
|
315
|
+
</SceneMotionLayer>
|
|
316
|
+
|
|
317
|
+
<SceneMotionLayer
|
|
318
|
+
variant="media"
|
|
319
|
+
style={{
|
|
320
|
+
position: 'absolute',
|
|
321
|
+
inset: 0,
|
|
322
|
+
transform: `translate3d(${imageShiftX}px, ${imageShiftY}px, 0) scale(${imageScale})`,
|
|
323
|
+
}}
|
|
324
|
+
>
|
|
325
|
+
{renderCoverMedia(image)}
|
|
326
|
+
</SceneMotionLayer>
|
|
327
|
+
|
|
328
|
+
<div
|
|
329
|
+
aria-hidden="true"
|
|
330
|
+
style={{
|
|
331
|
+
position: 'absolute',
|
|
332
|
+
inset: 0,
|
|
333
|
+
background:
|
|
334
|
+
sceneConfig.sceneType === 'animation'
|
|
335
|
+
? `radial-gradient(circle at 50% 20%, rgba(255,255,255,${spotlightOpacity}) 0%, rgba(255,255,255,0.04) 18%, rgba(2,6,23,0.05) 34%, rgba(2,6,23,${vignetteOpacity}) 100%)`
|
|
336
|
+
: 'linear-gradient(180deg, rgba(2,6,23,0.08) 0%, rgba(2,6,23,0.14) 38%, rgba(2,6,23,0.62) 100%)',
|
|
337
|
+
pointerEvents: 'none',
|
|
338
|
+
}}
|
|
339
|
+
/>
|
|
340
|
+
|
|
341
|
+
<div
|
|
342
|
+
aria-hidden="true"
|
|
343
|
+
style={{
|
|
344
|
+
position: 'absolute',
|
|
345
|
+
inset: 0,
|
|
346
|
+
border: '1px solid rgba(255,255,255,0.08)',
|
|
347
|
+
pointerEvents: 'none',
|
|
348
|
+
}}
|
|
349
|
+
/>
|
|
350
|
+
|
|
351
|
+
<div
|
|
352
|
+
style={{
|
|
353
|
+
position: 'absolute',
|
|
354
|
+
top: padding,
|
|
355
|
+
left: padding,
|
|
356
|
+
display: 'grid',
|
|
357
|
+
gap: 8,
|
|
358
|
+
zIndex: 2,
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
<LivePhotoBadge label={badge ?? (sceneConfig.sceneType === 'animation' ? 'Live Photo' : 'Photo')} />
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<div
|
|
365
|
+
style={{
|
|
366
|
+
position: 'absolute',
|
|
367
|
+
left: padding,
|
|
368
|
+
right: padding,
|
|
369
|
+
bottom: padding,
|
|
370
|
+
display: 'grid',
|
|
371
|
+
gap: 8,
|
|
372
|
+
zIndex: 2,
|
|
373
|
+
}}
|
|
374
|
+
>
|
|
375
|
+
{title ? (
|
|
376
|
+
<SceneMotionLayer variant="hero-title">
|
|
377
|
+
<div style={{ fontSize: titleSize, fontWeight: 800, letterSpacing: '-0.06em', lineHeight: 0.95 }}>
|
|
378
|
+
{title}
|
|
379
|
+
</div>
|
|
380
|
+
</SceneMotionLayer>
|
|
381
|
+
) : null}
|
|
382
|
+
{subtitle ? (
|
|
383
|
+
<SceneMotionLayer variant="hero-copy" delayIndex={1}>
|
|
384
|
+
<div style={{ fontSize: subtitleSize, lineHeight: 1.45, opacity: 0.84, maxWidth: portrait ? '88%' : '56%' }}>
|
|
385
|
+
{subtitle}
|
|
386
|
+
</div>
|
|
387
|
+
</SceneMotionLayer>
|
|
388
|
+
) : null}
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
)}
|
|
392
|
+
|
|
393
|
+
{variant === 'live' ? null : (
|
|
394
|
+
<div
|
|
395
|
+
style={{
|
|
396
|
+
display: 'flex',
|
|
397
|
+
justifyContent: 'space-between',
|
|
398
|
+
alignItems: 'center',
|
|
399
|
+
gap: 16,
|
|
400
|
+
minWidth: 0,
|
|
401
|
+
}}
|
|
402
|
+
>
|
|
403
|
+
<div
|
|
404
|
+
style={{
|
|
405
|
+
fontSize: Math.round(runtime.height * 0.014),
|
|
406
|
+
letterSpacing: '0.24em',
|
|
407
|
+
textTransform: 'uppercase',
|
|
408
|
+
opacity: 0.6,
|
|
409
|
+
minWidth: 0,
|
|
410
|
+
}}
|
|
411
|
+
>
|
|
412
|
+
{sceneConfig.sceneType === 'animation' ? 'Animation preview' : 'Static preview'}
|
|
413
|
+
</div>
|
|
414
|
+
<div
|
|
415
|
+
style={{
|
|
416
|
+
fontSize: Math.round(runtime.height * 0.014),
|
|
417
|
+
letterSpacing: '0.18em',
|
|
418
|
+
textTransform: 'uppercase',
|
|
419
|
+
opacity: 0.46,
|
|
420
|
+
minWidth: 0,
|
|
421
|
+
}}
|
|
422
|
+
>
|
|
423
|
+
{sceneConfig.sceneInFrames} frames
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
)}
|
|
427
|
+
</section>
|
|
428
|
+
</BaseRemotionScene>
|
|
429
|
+
)
|
|
430
|
+
},
|
|
431
|
+
}),
|
|
432
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react'
|
|
2
|
+
|
|
3
|
+
import { VideoComponent, type ScenePluginMetadata } from '@vibecuting/component-project-helper'
|
|
4
|
+
|
|
5
|
+
import { DefaultPhotoStripScene, componentMetadata as baseComponentMetadata } from './default-photo-strip-scene'
|
|
6
|
+
|
|
7
|
+
export const componentMetadata: ScenePluginMetadata = {
|
|
8
|
+
...baseComponentMetadata,
|
|
9
|
+
name: 'DefaultPhotoStripSceneHorizontal',
|
|
10
|
+
description: 'Apple-style photo strip scene (Portrait 9:16 variant)',
|
|
11
|
+
sourceFile: 'src/core/scene/photo/default-photo-strip-scene-Horizontal.tsx',
|
|
12
|
+
aspectRatio: '9:16',
|
|
13
|
+
tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DefaultPhotoStripSceneHorizontal = VideoComponent(componentMetadata)(function DefaultPhotoStripSceneHorizontal(
|
|
17
|
+
props: ComponentProps<typeof DefaultPhotoStripScene>,
|
|
18
|
+
) {
|
|
19
|
+
return <DefaultPhotoStripScene {...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 { DefaultPhotoStripScene, componentMetadata as baseComponentMetadata } from './default-photo-strip-scene'
|
|
6
|
+
|
|
7
|
+
export const componentMetadata: ScenePluginMetadata = {
|
|
8
|
+
...baseComponentMetadata,
|
|
9
|
+
name: 'DefaultPhotoStripSceneLandscape',
|
|
10
|
+
description: 'Apple-style photo strip scene (Landscape 16:9 variant)',
|
|
11
|
+
sourceFile: 'src/core/scene/photo/default-photo-strip-scene-Landscape.tsx',
|
|
12
|
+
aspectRatio: '16:9',
|
|
13
|
+
tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DefaultPhotoStripSceneLandscape = VideoComponent(componentMetadata)(function DefaultPhotoStripSceneLandscape(
|
|
17
|
+
props: ComponentProps<typeof DefaultPhotoStripScene>,
|
|
18
|
+
) {
|
|
19
|
+
return <DefaultPhotoStripScene {...props} />
|
|
20
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DefaultPhotoStripSceneHorizontal } from './default-photo-strip-scene-Horizontal'
|
|
2
|
+
import { DefaultPhotoStripSceneLandscape } from './default-photo-strip-scene-Landscape'
|
|
3
|
+
|
|
4
|
+
const photoUrl = 'https://heyaai-statics-1342239856.cos.ap-guangzhou.myqcloud.com/tmp/premium_photo-1779404552087-0f905162376f-121.jpeg'
|
|
5
|
+
const image = <img src={photoUrl} alt="Photo strip" />
|
|
6
|
+
|
|
7
|
+
const args = {
|
|
8
|
+
title: 'Three Beats',
|
|
9
|
+
subtitle: 'A tight strip of stills for storytelling, chapter markers, or memory sequencing.',
|
|
10
|
+
badge: 'Strip',
|
|
11
|
+
image,
|
|
12
|
+
images: [image, image, image],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: 'core/scene/photo/DefaultPhotoStripSceneLandscape',
|
|
17
|
+
component: DefaultPhotoStripSceneLandscape,
|
|
18
|
+
args,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default meta
|
|
22
|
+
|
|
23
|
+
export const Default = {
|
|
24
|
+
render: (storyArgs: typeof args) => <DefaultPhotoStripSceneLandscape {...storyArgs} />,
|
|
25
|
+
parameters: { scenePreviewAspectRatio: '16:9' },
|
|
26
|
+
}
|
|
27
|
+
export const Animated = {
|
|
28
|
+
render: (storyArgs: typeof args) => <DefaultPhotoStripSceneLandscape {...storyArgs} />,
|
|
29
|
+
parameters: { scenePreviewAspectRatio: '16:9', scenePreviewMode: 'animation' },
|
|
30
|
+
}
|
|
31
|
+
export const Horizontal = {
|
|
32
|
+
render: (storyArgs: typeof args) => <DefaultPhotoStripSceneHorizontal {...storyArgs} />,
|
|
33
|
+
parameters: { scenePreviewAspectRatio: '9:16' },
|
|
34
|
+
}
|
|
35
|
+
export const HorizontalAnimated = {
|
|
36
|
+
render: (storyArgs: typeof args) => <DefaultPhotoStripSceneHorizontal {...storyArgs} />,
|
|
37
|
+
parameters: { scenePreviewAspectRatio: '9:16', scenePreviewMode: 'animation' },
|
|
38
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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: 'DefaultPhotoStripScene',
|
|
10
|
+
description: 'Apple-style photo strip scene',
|
|
11
|
+
sourceFile: 'src/core/scene/photo/default-photo-strip-scene.tsx',
|
|
12
|
+
tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'strip', 'sequence'])),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const DefaultPhotoStripScene = VideoComponent(componentMetadata)(function DefaultPhotoStripScene(
|
|
16
|
+
props: ComponentProps<typeof DefaultLivePhotoScene>,
|
|
17
|
+
) {
|
|
18
|
+
return <DefaultLivePhotoScene {...props} variant="strip" />
|
|
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 { DefaultPhotoWallScene, componentMetadata as baseComponentMetadata } from './default-photo-wall-scene'
|
|
6
|
+
|
|
7
|
+
export const componentMetadata: ScenePluginMetadata = {
|
|
8
|
+
...baseComponentMetadata,
|
|
9
|
+
name: 'DefaultPhotoWallSceneHorizontal',
|
|
10
|
+
description: 'Apple-style photo wall scene (Portrait 9:16 variant)',
|
|
11
|
+
sourceFile: 'src/core/scene/photo/default-photo-wall-scene-Horizontal.tsx',
|
|
12
|
+
aspectRatio: '9:16',
|
|
13
|
+
tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'horizontal'])),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DefaultPhotoWallSceneHorizontal = VideoComponent(componentMetadata)(function DefaultPhotoWallSceneHorizontal(
|
|
17
|
+
props: ComponentProps<typeof DefaultPhotoWallScene>,
|
|
18
|
+
) {
|
|
19
|
+
return <DefaultPhotoWallScene {...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 { DefaultPhotoWallScene, componentMetadata as baseComponentMetadata } from './default-photo-wall-scene'
|
|
6
|
+
|
|
7
|
+
export const componentMetadata: ScenePluginMetadata = {
|
|
8
|
+
...baseComponentMetadata,
|
|
9
|
+
name: 'DefaultPhotoWallSceneLandscape',
|
|
10
|
+
description: 'Apple-style photo wall scene (Landscape 16:9 variant)',
|
|
11
|
+
sourceFile: 'src/core/scene/photo/default-photo-wall-scene-Landscape.tsx',
|
|
12
|
+
aspectRatio: '16:9',
|
|
13
|
+
tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'landscape'])),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DefaultPhotoWallSceneLandscape = VideoComponent(componentMetadata)(function DefaultPhotoWallSceneLandscape(
|
|
17
|
+
props: ComponentProps<typeof DefaultPhotoWallScene>,
|
|
18
|
+
) {
|
|
19
|
+
return <DefaultPhotoWallScene {...props} />
|
|
20
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DefaultPhotoWallSceneHorizontal } from './default-photo-wall-scene-Horizontal'
|
|
2
|
+
import { DefaultPhotoWallSceneLandscape } from './default-photo-wall-scene-Landscape'
|
|
3
|
+
|
|
4
|
+
const photoUrl = 'https://heyaai-statics-1342239856.cos.ap-guangzhou.myqcloud.com/tmp/premium_photo-1779404552087-0f905162376f-121.jpeg'
|
|
5
|
+
const image = <img src={photoUrl} alt="Photo wall" />
|
|
6
|
+
|
|
7
|
+
const args = {
|
|
8
|
+
title: 'Four Moments',
|
|
9
|
+
subtitle: 'A photo wall that can duplicate or mix multiple stills with a soft cinematic drift.',
|
|
10
|
+
badge: 'Wall',
|
|
11
|
+
image,
|
|
12
|
+
images: [image, image, image, image],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: 'core/scene/photo/DefaultPhotoWallSceneLandscape',
|
|
17
|
+
component: DefaultPhotoWallSceneLandscape,
|
|
18
|
+
args,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default meta
|
|
22
|
+
|
|
23
|
+
export const Default = {
|
|
24
|
+
render: (storyArgs: typeof args) => <DefaultPhotoWallSceneLandscape {...storyArgs} />,
|
|
25
|
+
parameters: { scenePreviewAspectRatio: '16:9' },
|
|
26
|
+
}
|
|
27
|
+
export const Animated = {
|
|
28
|
+
render: (storyArgs: typeof args) => <DefaultPhotoWallSceneLandscape {...storyArgs} />,
|
|
29
|
+
parameters: { scenePreviewAspectRatio: '16:9', scenePreviewMode: 'animation' },
|
|
30
|
+
}
|
|
31
|
+
export const Horizontal = {
|
|
32
|
+
render: (storyArgs: typeof args) => <DefaultPhotoWallSceneHorizontal {...storyArgs} />,
|
|
33
|
+
parameters: { scenePreviewAspectRatio: '9:16' },
|
|
34
|
+
}
|
|
35
|
+
export const HorizontalAnimated = {
|
|
36
|
+
render: (storyArgs: typeof args) => <DefaultPhotoWallSceneHorizontal {...storyArgs} />,
|
|
37
|
+
parameters: { scenePreviewAspectRatio: '9:16', scenePreviewMode: 'animation' },
|
|
38
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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: 'DefaultPhotoWallScene',
|
|
10
|
+
description: 'Apple-style photo wall scene',
|
|
11
|
+
sourceFile: 'src/core/scene/photo/default-photo-wall-scene.tsx',
|
|
12
|
+
tags: Array.from(new Set([...(baseComponentMetadata.tags ?? []), 'wall', 'collage'])),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const DefaultPhotoWallScene = VideoComponent(componentMetadata)(function DefaultPhotoWallScene(
|
|
16
|
+
props: ComponentProps<typeof DefaultLivePhotoScene>,
|
|
17
|
+
) {
|
|
18
|
+
return <DefaultLivePhotoScene {...props} variant="wall" />
|
|
19
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DefaultEffectsPhotoSceneHorizontal } from './default-effects-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/effects-photo/DefaultEffectsPhotoSceneHorizontal',
|
|
7
|
+
component: DefaultEffectsPhotoSceneHorizontal,
|
|
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: '#7c3aed',
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default meta
|
|
25
|
+
|
|
26
|
+
export const Default = {
|
|
27
|
+
parameters: {
|
|
28
|
+
scenePreviewAspectRatio: '9:16',
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const Animated = {
|
|
33
|
+
parameters: {
|
|
34
|
+
scenePreviewAspectRatio: '9:16',
|
|
35
|
+
scenePreviewMode: 'animation',
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const Both = {
|
|
40
|
+
parameters: {
|
|
41
|
+
scenePreviewAspectRatio: '9:16',
|
|
42
|
+
scenePreviewMode: 'both',
|
|
43
|
+
},
|
|
44
|
+
}
|