AI Image Studio Phase 1
This commit is contained in:
263
frontend/src/components/ImageStudio/dashboard/ModuleCard.tsx
Normal file
263
frontend/src/components/ImageStudio/dashboard/ModuleCard.tsx
Normal file
@@ -0,0 +1,263 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Stack,
|
||||
Typography,
|
||||
Chip,
|
||||
Button,
|
||||
Tooltip,
|
||||
Divider,
|
||||
} from '@mui/material';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import LaunchIcon from '@mui/icons-material/Launch';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import { alpha } from '@mui/material/styles';
|
||||
import { ModuleConfig } from './types';
|
||||
import { statusStyles } from './constants';
|
||||
import { ModuleInfoCard } from './ModuleInfoCard';
|
||||
import {
|
||||
CreateEffectPreview,
|
||||
EditEffectPreview,
|
||||
UpscaleEffectPreview,
|
||||
TransformEffectPreview,
|
||||
SocialOptimizerEffectPreview,
|
||||
ControlEffectPreview,
|
||||
} from './previews';
|
||||
|
||||
interface ModuleCardProps {
|
||||
module: ModuleConfig;
|
||||
isHovered: boolean;
|
||||
onMouseEnter: () => void;
|
||||
onMouseLeave: () => void;
|
||||
onNavigate: (route: string) => void;
|
||||
}
|
||||
|
||||
export const ModuleCard: React.FC<ModuleCardProps> = ({
|
||||
module,
|
||||
isHovered,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onNavigate,
|
||||
}) => {
|
||||
const status = statusStyles[module.status];
|
||||
const disabled = module.status !== 'live';
|
||||
const hasPreview =
|
||||
module.key === 'create' ||
|
||||
module.key === 'edit' ||
|
||||
module.key === 'upscale' ||
|
||||
module.key === 'transform' ||
|
||||
module.key === 'optimizer' ||
|
||||
module.key === 'control';
|
||||
|
||||
const renderPreview = () => {
|
||||
switch (module.key) {
|
||||
case 'create':
|
||||
return <CreateEffectPreview />;
|
||||
case 'edit':
|
||||
return <EditEffectPreview />;
|
||||
case 'upscale':
|
||||
return <UpscaleEffectPreview />;
|
||||
case 'transform':
|
||||
return <TransformEffectPreview />;
|
||||
case 'optimizer':
|
||||
return <SocialOptimizerEffectPreview />;
|
||||
case 'control':
|
||||
return <ControlEffectPreview />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
height: '100%',
|
||||
borderRadius: 4,
|
||||
p: 3,
|
||||
border: '1px solid rgba(255,255,255,0.06)',
|
||||
background: alpha('#111827', 0.8),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1.5,
|
||||
position: 'relative',
|
||||
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
|
||||
boxShadow: isHovered
|
||||
? '0 20px 45px rgba(124,58,237,0.25)'
|
||||
: '0 10px 25px rgba(15,23,42,0.35)',
|
||||
transform: isHovered ? 'translateY(-4px)' : 'translateY(0)',
|
||||
overflow: 'hidden',
|
||||
'&::after': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
background:
|
||||
module.key === 'create'
|
||||
? 'radial-gradient(circle at top, rgba(124,58,237,0.25), transparent 60%)'
|
||||
: module.key === 'edit'
|
||||
? 'linear-gradient(120deg, rgba(8,145,178,0.25), transparent)'
|
||||
: module.key === 'upscale'
|
||||
? 'linear-gradient(90deg, rgba(248,113,113,0.25), transparent)'
|
||||
: 'linear-gradient(120deg, rgba(59,130,246,0.15), transparent)',
|
||||
opacity: isHovered ? 1 : 0.35,
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
}}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Box
|
||||
sx={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: alpha('#6366f1', 0.2),
|
||||
color: '#c7d2fe',
|
||||
fontSize: 22,
|
||||
}}
|
||||
>
|
||||
{module.icon}
|
||||
</Box>
|
||||
<Stack spacing={0.5}>
|
||||
<Typography variant="h6" fontWeight={700}>
|
||||
{module.title}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{module.subtitle}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Chip
|
||||
label={status.label}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: alpha(status.color, 0.2),
|
||||
color: status.color,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: hasPreview
|
||||
? 'rgba(248,250,252,0.92)'
|
||||
: 'rgba(148,163,184,0.95)',
|
||||
}}
|
||||
>
|
||||
{module.description}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
|
||||
{module.highlights.map(item => (
|
||||
<Chip
|
||||
key={item}
|
||||
size="small"
|
||||
label={item}
|
||||
sx={{
|
||||
background: 'linear-gradient(120deg, rgba(99,102,241,0.35), rgba(14,165,233,0.35))',
|
||||
color: '#f1f5f9',
|
||||
border: '1px solid rgba(255,255,255,0.3)',
|
||||
fontWeight: 600,
|
||||
letterSpacing: 0.2,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
{hasPreview && (
|
||||
<>
|
||||
{renderPreview()}
|
||||
<ModuleInfoCard module={module} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{!hasPreview && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 16,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
background: 'rgba(15,23,42,0.92)',
|
||||
backdropFilter: 'blur(12px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
padding: 2,
|
||||
opacity: isHovered ? 1 : 0,
|
||||
pointerEvents: 'none',
|
||||
transition: 'opacity 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<Typography variant="overline" sx={{ color: '#a5b4fc', letterSpacing: 1 }}>
|
||||
Pricing & How it works
|
||||
</Typography>
|
||||
<Stack spacing={0.5}>
|
||||
<Typography variant="body2" fontWeight={700}>
|
||||
{module.pricing.estimate}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{module.pricing.notes}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ borderColor: 'rgba(255,255,255,0.08)' }} />
|
||||
<Typography variant="subtitle2" fontWeight={700}>
|
||||
{module.example.title}
|
||||
</Typography>
|
||||
<Stack component="ul" spacing={0.5} sx={{ pl: 2, m: 0 }}>
|
||||
{module.example.steps.map(step => (
|
||||
<Typography
|
||||
component="li"
|
||||
key={step}
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
>
|
||||
{step}
|
||||
</Typography>
|
||||
))}
|
||||
</Stack>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
ETA: {module.example.eta}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Stack direction="row" spacing={1} alignItems="center" mt="auto">
|
||||
<Tooltip title={module.help}>
|
||||
<InfoOutlinedIcon sx={{ color: 'rgba(255,255,255,0.6)', fontSize: 20 }} />
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={disabled}
|
||||
startIcon={disabled ? <LockIcon /> : <LaunchIcon />}
|
||||
onClick={() => {
|
||||
if (!disabled && module.route) {
|
||||
onNavigate(module.route);
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
borderRadius: 999,
|
||||
textTransform: 'none',
|
||||
fontWeight: 700,
|
||||
ml: 'auto',
|
||||
background: disabled
|
||||
? 'rgba(148,163,184,0.2)'
|
||||
: 'linear-gradient(90deg,#7c3aed,#2563eb)',
|
||||
}}
|
||||
>
|
||||
{disabled ? 'Coming Soon' : 'Open'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { Paper, Stack, Typography, Divider } from '@mui/material';
|
||||
import { ModuleConfig } from './types';
|
||||
|
||||
export const ModuleInfoCard: React.FC<{ module: ModuleConfig }> = ({ module }) => (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{
|
||||
mt: 1.5,
|
||||
borderRadius: 3,
|
||||
borderColor: 'rgba(255,255,255,0.12)',
|
||||
backgroundColor: 'rgba(15,23,42,0.65)',
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="caption" sx={{ color: '#a5b4fc', letterSpacing: 1 }}>
|
||||
Pricing & Workflow
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight={600}>
|
||||
{module.pricing.estimate}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{module.pricing.notes}
|
||||
</Typography>
|
||||
<Divider sx={{ borderColor: 'rgba(255,255,255,0.08)' }} />
|
||||
<Typography variant="subtitle2" fontWeight={700}>
|
||||
{module.example.title}
|
||||
</Typography>
|
||||
<Stack component="ul" spacing={0.5} sx={{ pl: 2, m: 0 }}>
|
||||
{module.example.steps.map(step => (
|
||||
<Typography key={step} component="li" variant="body2" color="text.secondary">
|
||||
{step}
|
||||
</Typography>
|
||||
))}
|
||||
</Stack>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
ETA: {module.example.eta}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
|
||||
93
frontend/src/components/ImageStudio/dashboard/constants.ts
Normal file
93
frontend/src/components/ImageStudio/dashboard/constants.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
export const createExamples = [
|
||||
{
|
||||
id: 'ig-hero',
|
||||
label: 'Instagram hero',
|
||||
prompt:
|
||||
'"Cinematic coffee shop hero shot, golden hour lighting, stylish barista pouring latte art, 4k, depth of field, film grain"',
|
||||
provider: 'WaveSpeed Ideogram V3 Turbo',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1509042239860-f550ce710b93?auto=format&fit=crop&w=1200&q=80',
|
||||
description:
|
||||
'Polished hero visual for carousel slides and blog headers with photorealistic signage.',
|
||||
price: '$0.18',
|
||||
eta: '~4s',
|
||||
},
|
||||
{
|
||||
id: 'linkedin-thought',
|
||||
label: 'LinkedIn thought-leadership',
|
||||
prompt:
|
||||
'"Minimalist workspace flat lay, teal gradients, AI workflow diagrams, overhead view, ultra clean, 8k render"',
|
||||
provider: 'Gemini Imagen',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1487017159836-4e23ece2e4cf?auto=format&fit=crop&w=1200&q=80',
|
||||
description: 'Clean layout for LinkedIn posts that need professional, text-friendly framing.',
|
||||
price: '$0.11',
|
||||
eta: '~3s',
|
||||
},
|
||||
{
|
||||
id: 'tiktok-hook',
|
||||
label: 'TikTok hook frame',
|
||||
prompt:
|
||||
'"Vibrant neon studio, bold typography reading Growth Hacks, 9:16 layout, dynamic lighting, energetic vibe"',
|
||||
provider: 'WaveSpeed Qwen Image',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1504196606672-aef5c9cefc92?auto=format&fit=crop&w=1200&q=80',
|
||||
description: 'High-energy vertical frame to start TikTok/Reels with bold colors and legible copy.',
|
||||
price: '$0.07',
|
||||
eta: '~2s',
|
||||
},
|
||||
];
|
||||
|
||||
export const upscaleSamples = {
|
||||
lowRes: 'https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=600&q=30',
|
||||
hiRes: 'https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=1600&q=80',
|
||||
};
|
||||
|
||||
export const transformAssets = {
|
||||
storyboard: '/images/scene_1_Welcome_to_the_Cloud_Kitchen___ae6436d9.png',
|
||||
video: '/videos/scene_1_user_33Gz1FPI86V_0a5d0d71.mp4',
|
||||
script:
|
||||
"Welcome to the Cloud Kitchen! Meet Ava, your virtual chef companion. Let's explore how she runs three delivery brands from one AI-powered hub.",
|
||||
};
|
||||
|
||||
export const controlAssets = {
|
||||
inputImage: '/images/scene_1_Welcome_to_the_Cloud_Kitchen___ae6436d9.png',
|
||||
outputVideo: '/videos/text-video-voiceover.mp4',
|
||||
prompt:
|
||||
"A confident woman in her 40s stands on a stage with a microphone. The background shows a large LED screen with abstract visuals. She smiles and begins speaking to the audience: \"Good evening everyone. Tonight, I want to share three powerful lessons about leadership and innovation.\" Her lip movements match her voice, and she uses expressive hand gestures while speaking.",
|
||||
seed: 2133312826,
|
||||
resolution: '720p',
|
||||
duration: 5,
|
||||
};
|
||||
|
||||
export const editBeforeAfter = [
|
||||
{
|
||||
before: 'https://images.unsplash.com/photo-1455587734955-081b22074882?auto=format&fit=crop&w=800&q=80',
|
||||
after: 'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?auto=format&fit=crop&w=800&q=80',
|
||||
prompt: 'Inpainted background swap with studio lighting and relit subject',
|
||||
},
|
||||
{
|
||||
before: 'https://images.unsplash.com/photo-1472506200026-38c43d5fbf97?auto=format&fit=crop&w=800&q=80',
|
||||
after: 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?auto=format&fit=crop&w=800&q=80',
|
||||
prompt: 'Recolored wardrobe + added morning haze',
|
||||
},
|
||||
{
|
||||
before: 'https://images.unsplash.com/photo-1434389677669-e08b4cac3105?auto=format&fit=crop&w=800&q=80',
|
||||
after: 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=800&q=80',
|
||||
prompt: 'Reframed hero crop with dramatic sky replacement',
|
||||
},
|
||||
];
|
||||
|
||||
export const platformPresets = [
|
||||
{ label: 'IG Feed 1:1', top: '10%', left: '5%', width: '35%', height: '35%' },
|
||||
{ label: 'TikTok 9:16', top: '5%', right: '5%', width: '25%', height: '60%' },
|
||||
{ label: 'LinkedIn 1.91:1', bottom: '8%', left: '10%', width: '55%', height: '25%' },
|
||||
{ label: 'Pinterest 2:3', bottom: '12%', right: '8%', width: '22%', height: '30%' },
|
||||
];
|
||||
|
||||
export const statusStyles = {
|
||||
live: { label: 'Live', color: '#10b981' },
|
||||
'coming soon': { label: 'Coming Soon', color: '#f97316' },
|
||||
planning: { label: 'In Planning', color: '#d1d5db' },
|
||||
};
|
||||
|
||||
7
frontend/src/components/ImageStudio/dashboard/index.ts
Normal file
7
frontend/src/components/ImageStudio/dashboard/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './types';
|
||||
export * from './constants';
|
||||
export { ModuleInfoCard } from './ModuleInfoCard';
|
||||
export { ModuleCard } from './ModuleCard';
|
||||
export { studioModules } from './modules';
|
||||
export * from './previews';
|
||||
|
||||
208
frontend/src/components/ImageStudio/dashboard/modules.tsx
Normal file
208
frontend/src/components/ImageStudio/dashboard/modules.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
import React from 'react';
|
||||
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
|
||||
import BrushIcon from '@mui/icons-material/Brush';
|
||||
import UpgradeIcon from '@mui/icons-material/Upgrade';
|
||||
import TransformIcon from '@mui/icons-material/Transform';
|
||||
import ShareIcon from '@mui/icons-material/Share';
|
||||
import EditNoteIcon from '@mui/icons-material/EditNote';
|
||||
import LibraryBooksIcon from '@mui/icons-material/LibraryBooks';
|
||||
import { ModuleConfig } from './types';
|
||||
|
||||
export const studioModules: ModuleConfig[] = [
|
||||
{
|
||||
key: 'create',
|
||||
title: 'Create Studio',
|
||||
subtitle: 'Text-to-image generation',
|
||||
description:
|
||||
'Generate photorealistic visuals with Stability, WaveSpeed, HuggingFace, and Gemini. Templates, smart providers, and enterprise prompt controls included.',
|
||||
highlights: ['Smart provider routing', 'Platform templates', 'Cost preview'],
|
||||
status: 'live',
|
||||
route: '/image-generator',
|
||||
icon: <AutoAwesomeIcon />,
|
||||
help: 'Ideal for blog headers, social posts, ad creatives, and brand assets.',
|
||||
pricing: {
|
||||
estimate: '$0.12 - $0.48 / image (credit aware)',
|
||||
notes: 'Auto-select suggests lowest-cost provider before generation.',
|
||||
},
|
||||
example: {
|
||||
title: 'Instagram carousel hero image',
|
||||
steps: [
|
||||
'Choose Instagram template + 4:5 ratio',
|
||||
'Prompt helper enriches "fall coffee launch" copy',
|
||||
'Preview cost/time → generate 3 variations',
|
||||
],
|
||||
eta: '~4s per variation',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
title: 'Edit Studio',
|
||||
subtitle: 'AI-powered editing',
|
||||
description:
|
||||
'Remove backgrounds, inpaint, outpaint, recolor, and relight images with Stability AI workflows and Hugging Face conversational edits.',
|
||||
highlights: ['Object removal', 'Canvas expansion', 'Relight + background swap'],
|
||||
status: 'live',
|
||||
route: '/image-editor',
|
||||
icon: <BrushIcon />,
|
||||
help: 'Upload existing assets and enhance them with precise AI tools.',
|
||||
pricing: {
|
||||
estimate: '$0.08 - $0.30 / edit (based on area + ops)',
|
||||
notes: 'Bulk edits share the same upload to save credits.',
|
||||
},
|
||||
example: {
|
||||
title: 'Replace dull background for LinkedIn hero',
|
||||
steps: [
|
||||
'Upload portrait → auto mask detects subject',
|
||||
'Use "Replace background" preset → choose corporate loft style',
|
||||
'Relight + save layered history for future tweaks',
|
||||
],
|
||||
eta: '~6s render',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'upscale',
|
||||
title: 'Upscale Studio',
|
||||
subtitle: 'Resolution enhancement',
|
||||
description:
|
||||
'Fast 4x upscale, conservative 4K, and creative 4K pipelines powered by Stability AI. Perfect for print, campaigns, and hero imagery.',
|
||||
highlights: ['Fast 4x mode', '4K creative', 'Side-by-side preview'],
|
||||
status: 'live',
|
||||
route: '/image-upscale',
|
||||
icon: <UpgradeIcon />,
|
||||
help: 'Upscale images to 4K-ready assets with one click.',
|
||||
pricing: {
|
||||
estimate: '$0.10 (Fast) · $0.32 (Creative 4K)',
|
||||
notes: 'Queue batches overnight to reduce credit burn.',
|
||||
},
|
||||
example: {
|
||||
title: 'Print-ready hero panel',
|
||||
steps: [
|
||||
'Upload 1024 hero → auto-detect recommends Creative 4K',
|
||||
'Preview side-by-side → confirm texture preservation',
|
||||
'Schedule overnight batch with 6 variants',
|
||||
],
|
||||
eta: 'Fast = 1s · 4K = 6s',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'transform',
|
||||
title: 'Transform Studio',
|
||||
subtitle: 'Image → Video / Avatar / 3D',
|
||||
description:
|
||||
'WaveSpeed WAN 2.5 (image-to-video), Hunyuan Avatar, and Stable Fast 3D to convert images into motion, avatars, or 3D assets.',
|
||||
highlights: ['Image-to-video', 'Talking avatars', '3D export'],
|
||||
status: 'coming soon',
|
||||
icon: <TransformIcon />,
|
||||
help: 'Designed for campaign teasers, explainers, and immersive media.',
|
||||
pricing: {
|
||||
estimate: '$0.50 (10s video 480p) · $3.60 (avatar 2 min)',
|
||||
notes: 'Text-to-speech add-on billed separately per 15s.',
|
||||
},
|
||||
example: {
|
||||
title: 'Product launch teaser video',
|
||||
steps: [
|
||||
'Pick motion preset "Medium pan + glow"',
|
||||
'Upload hero shot + 8s script for TTS',
|
||||
'Preview storyboard → export 1080p MP4',
|
||||
],
|
||||
eta: '~15s generation',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'optimizer',
|
||||
title: 'Social Optimizer',
|
||||
subtitle: 'Platform-ready exports',
|
||||
description:
|
||||
'Smart resize, safe zones, and engagement tips for Instagram, TikTok, LinkedIn, YouTube, Pinterest, and more in one click.',
|
||||
highlights: ['Text safe zones', 'Batch export', 'Platform presets'],
|
||||
status: 'planning',
|
||||
icon: <ShareIcon />,
|
||||
help: 'Ship consistent assets across every social surface.',
|
||||
pricing: {
|
||||
estimate: '$0.02 - $0.06 / rendition',
|
||||
notes: 'Unlimited exports on Pro + Enterprise tiers.',
|
||||
},
|
||||
example: {
|
||||
title: 'One hero → 6 platform exports',
|
||||
steps: [
|
||||
'Add source image → auto-detect focal subject',
|
||||
'Select IG, TikTok, LinkedIn, Pinterest presets',
|
||||
'Review safe zones overlay → export ZIP + schedule',
|
||||
],
|
||||
eta: '~2s / platform',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'control',
|
||||
title: 'Control Studio',
|
||||
subtitle: 'Sketch, structure & style',
|
||||
description:
|
||||
'Sketch-to-image, structure control, and advanced style transfer so creative directors can steer outputs precisely.',
|
||||
highlights: ['Sketch control', 'Style libraries', 'Strength sliders'],
|
||||
status: 'planning',
|
||||
icon: <EditNoteIcon />,
|
||||
help: 'For art directors who need total control over AI outputs.',
|
||||
pricing: {
|
||||
estimate: '$0.20 / render with dual-control',
|
||||
notes: 'Saved reference boards reuse controls at $0.05.',
|
||||
},
|
||||
example: {
|
||||
title: 'Storyboard consistency pack',
|
||||
steps: [
|
||||
'Upload wireframe + art-style JPEG',
|
||||
'Set control strength 60% structure / 40% style',
|
||||
'Generate 8 shots → auto-tag to Asset Library',
|
||||
],
|
||||
eta: '~8s per shot',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'batch',
|
||||
title: 'Batch Processor',
|
||||
subtitle: 'Scale campaigns',
|
||||
description:
|
||||
'Queue generators, edits, upscales, and exports for entire campaigns with cost previews, scheduling, and monitoring.',
|
||||
highlights: ['Bulk prompts', 'Usage tracking', 'Schedule windows'],
|
||||
status: 'planning',
|
||||
icon: <LibraryBooksIcon />,
|
||||
help: 'Turn one brief into dozens of deliverables automatically.',
|
||||
pricing: {
|
||||
estimate: 'Dynamic · e.g. 25-image pack ≈ $9',
|
||||
notes: 'Warns when batch exceeds remaining credits.',
|
||||
},
|
||||
example: {
|
||||
title: 'Evergreen blog refresh',
|
||||
steps: [
|
||||
'Upload CSV prompts grouped by persona',
|
||||
'Assign module per row (Create, Edit, Upscale)',
|
||||
'Schedule weekend window + email digest',
|
||||
],
|
||||
eta: 'Depends on queue size',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'library',
|
||||
title: 'Asset Library',
|
||||
subtitle: 'Searchable visual archive',
|
||||
description:
|
||||
'AI-tagged collections, favorites, history, and collaboration. Filters by platform, persona, use case, or campaign.',
|
||||
highlights: ['AI tagging', 'Version history', 'Shareable collections'],
|
||||
status: 'planning',
|
||||
icon: <LibraryBooksIcon />,
|
||||
help: 'Centralize every visual produced inside ALwrity.',
|
||||
pricing: {
|
||||
estimate: 'Included in tier · extra storage $5 / 100GB',
|
||||
notes: 'Enterprise adds S3 export + governance logs.',
|
||||
},
|
||||
example: {
|
||||
title: 'Campaign war-room board',
|
||||
steps: [
|
||||
'Filter by persona + platform → pin hero assets',
|
||||
'Share read-only board with agency partner',
|
||||
'Track usage + cost per asset inside analytics tab',
|
||||
],
|
||||
eta: 'Instant search (<500ms)',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, Typography, Chip, Button } from '@mui/material';
|
||||
import { controlAssets } from '../constants';
|
||||
|
||||
export const ControlEffectPreview: React.FC = () => {
|
||||
const [videoKey, setVideoKey] = React.useState(0);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
background: 'rgba(15,23,42,0.5)',
|
||||
p: { xs: 2, md: 3 },
|
||||
}}
|
||||
>
|
||||
<Stack direction={{ xs: 'column', md: 'row' }} spacing={2} alignItems="stretch">
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.12)',
|
||||
background: 'linear-gradient(135deg,#8b5cf6,#a855f7)',
|
||||
color: '#f3e8ff',
|
||||
p: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1.5,
|
||||
}}
|
||||
>
|
||||
<Typography variant="overline" sx={{ letterSpacing: 2, color: '#e9d5ff' }}>
|
||||
Control Input
|
||||
</Typography>
|
||||
<Box
|
||||
component="img"
|
||||
src={controlAssets.inputImage}
|
||||
alt="Control reference"
|
||||
sx={{ width: '100%', borderRadius: 2, border: '2px solid rgba(255,255,255,0.2)', boxShadow: '0 10px 25px rgba(139,92,246,0.3)' }}
|
||||
/>
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="caption" sx={{ color: '#e9d5ff', fontWeight: 600 }}>
|
||||
Prompt
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ fontSize: '0.875rem', lineHeight: 1.5 }}>
|
||||
{controlAssets.prompt}
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap">
|
||||
<Chip
|
||||
size="small"
|
||||
label={`Seed ${controlAssets.seed}`}
|
||||
sx={{ background: 'rgba(255,255,255,0.2)', color: '#0f172a', borderRadius: 999 }}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
label={controlAssets.resolution}
|
||||
sx={{ background: 'rgba(255,255,255,0.2)', color: '#0f172a', borderRadius: 999 }}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
label={`${controlAssets.duration}s`}
|
||||
sx={{ background: 'rgba(255,255,255,0.2)', color: '#0f172a', borderRadius: 999 }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1.5,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.12)',
|
||||
background: '#020617',
|
||||
p: { xs: 1, md: 2 },
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1 }}>
|
||||
<Typography variant="overline" sx={{ letterSpacing: 2, color: '#a78bfa' }}>
|
||||
Generated Output
|
||||
</Typography>
|
||||
<Chip
|
||||
label="WAN 2.5"
|
||||
size="small"
|
||||
sx={{ background: 'rgba(167,139,250,0.15)', color: '#a78bfa', borderRadius: 999 }}
|
||||
/>
|
||||
<Button size="small" onClick={() => setVideoKey(prev => prev + 1)} sx={{ ml: 'auto', color: '#a78bfa', textTransform: 'none' }}>
|
||||
Reset preview
|
||||
</Button>
|
||||
</Stack>
|
||||
<Box
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<video key={videoKey} controls poster={controlAssets.inputImage} style={{ width: '100%', display: 'block' }} src={controlAssets.outputVideo} />
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
left: 12,
|
||||
background: 'rgba(15,23,42,0.7)',
|
||||
borderRadius: 999,
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
color: '#f8fafc',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
Voiceover · {controlAssets.duration}s
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1.5 }}>
|
||||
Alibaba WAN 2.5 converts text or images into videos (480p/720p/1080p) with synced audio, faster and more affordable than Google Veo3.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, Typography, Chip } from '@mui/material';
|
||||
import { createExamples } from '../constants';
|
||||
|
||||
export const CreateEffectPreview: React.FC = () => {
|
||||
const [textHovered, setTextHovered] = React.useState(false);
|
||||
const [exampleIndex, setExampleIndex] = React.useState(0);
|
||||
const example = createExamples[exampleIndex];
|
||||
const imageWidth = textHovered ? '20%' : '70%';
|
||||
const textWidth = textHovered ? '80%' : '30%';
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
borderRadius: 3,
|
||||
border: '3px solid',
|
||||
borderImage:
|
||||
'linear-gradient(135deg, rgba(124,58,237,0.8), rgba(14,165,233,0.8), rgba(16,185,129,0.8)) 1',
|
||||
overflow: 'hidden',
|
||||
height: { xs: 240, md: 280 },
|
||||
display: 'flex',
|
||||
background: '#0f172a',
|
||||
mt: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flex: '0 0 auto',
|
||||
width: imageWidth,
|
||||
transition: 'width 0.4s ease, filter 0.4s ease',
|
||||
backgroundImage: `url(${example.image})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
filter: textHovered ? 'saturate(1.1)' : 'saturate(1)',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 16,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
background: 'rgba(15,23,42,0.8)',
|
||||
borderRadius: 999,
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
boxShadow: '0 10px 20px rgba(2,6,23,0.45)',
|
||||
}}
|
||||
>
|
||||
{createExamples.map((_, idx) => (
|
||||
<Box
|
||||
key={_.id}
|
||||
onClick={() => setExampleIndex(idx)}
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 10,
|
||||
borderRadius: 999,
|
||||
background: idx === exampleIndex ? '#c4b5fd' : 'rgba(255,255,255,0.3)',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.2s ease',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
flex: '0 0 auto',
|
||||
width: textWidth,
|
||||
background: 'rgba(248,250,252,0.95)',
|
||||
color: '#0f172a',
|
||||
p: 3,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
boxShadow: '-12px 0 24px rgba(15,23,42,0.25)',
|
||||
transition: 'width 0.4s ease',
|
||||
}}
|
||||
onMouseEnter={() => setTextHovered(true)}
|
||||
onMouseLeave={() => setTextHovered(false)}
|
||||
>
|
||||
<Stack spacing={0.5} sx={{ overflowY: textHovered ? 'auto' : 'hidden', pr: 1 }}>
|
||||
<Typography variant="overline" sx={{ letterSpacing: 1.5, color: '#818cf8' }}>
|
||||
{example.label}
|
||||
</Typography>
|
||||
<Typography variant="subtitle2" fontWeight={700}>
|
||||
Prompt
|
||||
</Typography>
|
||||
<Typography variant="body2">{example.prompt}</Typography>
|
||||
<Typography variant="body2">{example.description}</Typography>
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap">
|
||||
<Chip
|
||||
size="small"
|
||||
label={`Price ${example.price}`}
|
||||
sx={{ background: '#ede9fe', color: '#4c1d95', borderRadius: 999, fontWeight: 600 }}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
label={`Turnaround ${example.eta}`}
|
||||
sx={{ background: '#cffafe', color: '#0f766e', borderRadius: 999, fontWeight: 600 }}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
label={example.provider}
|
||||
sx={{ background: '#dcfce7', color: '#166534', borderRadius: 999, fontWeight: 600 }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, Typography, Chip, Tooltip } from '@mui/material';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import { editBeforeAfter } from '../constants';
|
||||
|
||||
export const EditEffectPreview: React.FC = () => {
|
||||
const [exampleIndex, setExampleIndex] = React.useState(0);
|
||||
const pair = editBeforeAfter[exampleIndex];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
background: 'rgba(15,23,42,0.5)',
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 2 }}>
|
||||
<Typography variant="overline" sx={{ color: '#fcd34d', letterSpacing: 2 }}>
|
||||
Before → After
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title={
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="body2">
|
||||
Hover to reveal the original upload vs. AI-edited output. Perfect for showing background swaps,
|
||||
inpainting, or relighting.
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap">
|
||||
{['Erase objects cleanly', 'Smart relight', 'Replace backgrounds'].map(label => (
|
||||
<Chip
|
||||
key={label}
|
||||
size="small"
|
||||
label={label}
|
||||
sx={{ background: 'rgba(236,252,203,0.12)', color: '#fef3c7', borderRadius: 999 }}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<InfoOutlinedIcon sx={{ fontSize: 18, color: 'rgba(252,211,77,0.85)', cursor: 'pointer' }} />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<Box
|
||||
sx={{
|
||||
'--gap': '8px',
|
||||
display: 'grid',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
borderRadius: 3,
|
||||
overflow: 'hidden',
|
||||
border: '4px solid #22d3ee',
|
||||
minHeight: { xs: 260, md: 300 },
|
||||
'& > img': {
|
||||
'--progress': 'calc(-1 * var(--gap))',
|
||||
gridArea: '1 / 1',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
transition: 'clip-path 0.4s 0.1s',
|
||||
},
|
||||
'& > img:first-of-type': {
|
||||
clipPath: 'polygon(0 0, calc(100% + var(--progress)) 0, 0 calc(100% + var(--progress)))',
|
||||
},
|
||||
'& > img:last-of-type': {
|
||||
clipPath: 'polygon(100% 100%, 100% calc(0% - var(--progress)), calc(0% - var(--progress)) 100%)',
|
||||
},
|
||||
'&:hover > img:last-of-type, &:hover > img:first-of-type:hover': {
|
||||
'--progress': 'calc(50% - var(--gap))',
|
||||
},
|
||||
'&:hover > img:first-of-type, &:hover > img:first-of-type:hover + img': {
|
||||
'--progress': 'calc(-50% - var(--gap))',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box component="img" src={pair.before} alt="Original asset" />
|
||||
<Box component="img" src={pair.after} alt="Edited asset" />
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
right: 12,
|
||||
background: 'rgba(15,23,42,0.8)',
|
||||
borderRadius: 999,
|
||||
px: 1,
|
||||
py: 0.5,
|
||||
boxShadow: '0 10px 20px rgba(2,6,23,0.5)',
|
||||
}}
|
||||
>
|
||||
{editBeforeAfter.map((_, idx) => (
|
||||
<Box
|
||||
key={idx}
|
||||
onClick={() => setExampleIndex(idx)}
|
||||
sx={{
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: '50%',
|
||||
background: idx === exampleIndex ? '#f472b6' : 'rgba(255,255,255,0.4)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 12,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
background: 'rgba(15,23,42,0.85)',
|
||||
px: 1.5,
|
||||
py: 0.75,
|
||||
borderRadius: 999,
|
||||
boxShadow: '0 10px 25px rgba(2,6,23,0.6)',
|
||||
}}
|
||||
>
|
||||
<Chip label="Original" size="small" sx={{ background: '#fef3c7', color: '#78350f', fontWeight: 600 }} />
|
||||
<Chip label="Edited" size="small" sx={{ background: '#a5b4fc', color: '#1e1b4b', fontWeight: 600 }} />
|
||||
</Stack>
|
||||
</Box>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1.5 }}>
|
||||
Prompt used: {pair.prompt}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, Typography, Chip } from '@mui/material';
|
||||
import { transformAssets, platformPresets } from '../constants';
|
||||
|
||||
export const SocialOptimizerEffectPreview: React.FC = () => (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
background: 'rgba(15,23,42,0.5)',
|
||||
p: { xs: 2, md: 3 },
|
||||
}}
|
||||
>
|
||||
<Typography variant="overline" sx={{ letterSpacing: 2, color: '#fcd34d' }}>
|
||||
Platform Auto-Crop
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Smart resize finds the focal point and generates safe-zone aware crops for every surface.
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
background: '#020617',
|
||||
p: 2,
|
||||
position: 'relative',
|
||||
minHeight: 280,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={transformAssets.storyboard}
|
||||
alt="Source creative"
|
||||
sx={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: 2, filter: 'brightness(0.8)' }}
|
||||
/>
|
||||
{platformPresets.map(frame => (
|
||||
<Box
|
||||
key={frame.label}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
border: '2px solid rgba(248,250,252,0.8)',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0 10px 20px rgba(2,6,23,0.45)',
|
||||
transition: 'transform 0.2s ease',
|
||||
cursor: 'pointer',
|
||||
'&:hover': { transform: 'scale(1.05)' },
|
||||
...frame,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: -24,
|
||||
left: 0,
|
||||
background: 'rgba(15,23,42,0.85)',
|
||||
color: '#f8fafc',
|
||||
px: 1,
|
||||
py: 0.25,
|
||||
borderRadius: 999,
|
||||
fontSize: 11,
|
||||
}}
|
||||
>
|
||||
{frame.label}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap" sx={{ mt: 2 }}>
|
||||
{['Safe zones', 'Focal cropping', 'Batch export'].map(label => (
|
||||
<Chip key={label} size="small" label={label} sx={{ background: 'rgba(15,118,110,0.2)', color: '#5eead4', borderRadius: 999 }} />
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, Typography, Chip, Button } from '@mui/material';
|
||||
import { transformAssets } from '../constants';
|
||||
|
||||
export const TransformEffectPreview: React.FC = () => {
|
||||
const [videoKey, setVideoKey] = React.useState(0);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
background: 'rgba(15,23,42,0.5)',
|
||||
p: { xs: 2, md: 3 },
|
||||
}}
|
||||
>
|
||||
<Stack direction={{ xs: 'column', md: 'row' }} spacing={2} alignItems="stretch">
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.12)',
|
||||
background: 'linear-gradient(135deg,#0ea5e9,#6366f1)',
|
||||
color: '#e0f2fe',
|
||||
p: 2,
|
||||
minHeight: 260,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1.2,
|
||||
}}
|
||||
>
|
||||
<Typography variant="overline" sx={{ letterSpacing: 2, color: '#cffafe' }}>
|
||||
Storyboard Prompt
|
||||
</Typography>
|
||||
<Typography variant="body2">{transformAssets.script}</Typography>
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap">
|
||||
{['Image-to-video', 'WAN 2.5', '10s duration'].map(label => (
|
||||
<Chip
|
||||
key={label}
|
||||
size="small"
|
||||
label={label}
|
||||
sx={{ background: 'rgba(255,255,255,0.2)', color: '#0f172a', borderRadius: 999 }}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
<Box
|
||||
sx={{
|
||||
mt: 'auto',
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(255,255,255,0.25)',
|
||||
boxShadow: '0 20px 45px rgba(2,6,23,0.45)',
|
||||
}}
|
||||
>
|
||||
<Box component="img" src={transformAssets.storyboard} alt="Storyboard still" sx={{ width: '100%', display: 'block' }} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1.5,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.12)',
|
||||
background: '#020617',
|
||||
p: { xs: 1, md: 2 },
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1 }}>
|
||||
<Typography variant="overline" sx={{ letterSpacing: 2, color: '#38bdf8' }}>
|
||||
Render Preview
|
||||
</Typography>
|
||||
<Chip
|
||||
label="1080p"
|
||||
size="small"
|
||||
sx={{ background: 'rgba(56,189,248,0.15)', color: '#38bdf8', borderRadius: 999 }}
|
||||
/>
|
||||
<Button size="small" onClick={() => setVideoKey(prev => prev + 1)} sx={{ ml: 'auto', color: '#38bdf8', textTransform: 'none' }}>
|
||||
Reset preview
|
||||
</Button>
|
||||
</Stack>
|
||||
<Box
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<video key={videoKey} controls poster={transformAssets.storyboard} style={{ width: '100%', display: 'block' }} src={transformAssets.video} />
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
left: 12,
|
||||
background: 'rgba(15,23,42,0.7)',
|
||||
borderRadius: 999,
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
color: '#f8fafc',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
Scene 1 · Cloud Kitchen
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1.5 }}>
|
||||
Convert hero images into narrated clips with motion presets, subtitles, and audio uploads.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, Typography, Chip } from '@mui/material';
|
||||
import { upscaleSamples } from '../constants';
|
||||
|
||||
export const UpscaleEffectPreview: React.FC = () => (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
background: 'rgba(15,23,42,0.5)',
|
||||
p: { xs: 2, md: 3 },
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1} alignItems="center" justifyContent="space-between">
|
||||
<Box>
|
||||
<Typography variant="overline" sx={{ letterSpacing: 2, color: '#f9a8d4' }}>
|
||||
4× Upscale Showcase
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Flip the panels to compare the low-res upload with the 4K-ready output.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Chip
|
||||
label="Fast vs Creative"
|
||||
size="small"
|
||||
sx={{ background: 'rgba(236,72,153,0.15)', color: '#f9a8d4', borderRadius: 999 }}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
display: 'flex',
|
||||
gap: 2,
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap',
|
||||
'&:hover .flip-left': { transform: 'rotateY(-180deg)' },
|
||||
'&:hover .flip-right': { transform: 'rotateY(180deg)' },
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{ key: 'low', label: 'Before 600×400', className: 'flip-left', image: upscaleSamples.lowRes },
|
||||
{ key: 'high', label: 'After 2400×1600', className: 'flip-right', image: upscaleSamples.hiRes },
|
||||
].map(card => (
|
||||
<Box key={card.key} sx={{ perspective: 1000, width: { xs: 140, sm: 180 }, height: { xs: 200, sm: 240 } }}>
|
||||
<Box
|
||||
className={card.className}
|
||||
sx={{ position: 'relative', width: '100%', height: '100%', transition: '0.6s', transformStyle: 'preserve-3d' }}
|
||||
>
|
||||
<Box
|
||||
className="front"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
backfaceVisibility: 'hidden',
|
||||
borderRadius: 3,
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
background:
|
||||
card.key === 'low'
|
||||
? 'linear-gradient(135deg,#4c1d95,#9333ea)'
|
||||
: 'linear-gradient(135deg,#0f766e,#14b8a6)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: card.key === 'low' ? 'flex-end' : 'flex-start',
|
||||
px: 2,
|
||||
color: '#fff',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: '50%',
|
||||
background: 'rgba(255,255,255,0.2)',
|
||||
border: '2px solid rgba(255,255,255,0.8)',
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ ml: card.key === 'low' ? -6 : 2, mr: card.key === 'low' ? 2 : -6 }}>
|
||||
{card.label}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
className="back"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
backfaceVisibility: 'hidden',
|
||||
borderRadius: 3,
|
||||
transform: 'rotateY(180deg)',
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(255,255,255,0.15)',
|
||||
}}
|
||||
>
|
||||
<Box component="img" src={card.image} alt={card.label} sx={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1.5 }}>
|
||||
Try creative upscaling for texture enhancement, or fast mode for previews.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export { CreateEffectPreview } from './CreateEffectPreview';
|
||||
export { EditEffectPreview } from './EditEffectPreview';
|
||||
export { UpscaleEffectPreview } from './UpscaleEffectPreview';
|
||||
export { TransformEffectPreview } from './TransformEffectPreview';
|
||||
export { SocialOptimizerEffectPreview } from './SocialOptimizerEffectPreview';
|
||||
export { ControlEffectPreview } from './ControlEffectPreview';
|
||||
|
||||
25
frontend/src/components/ImageStudio/dashboard/types.ts
Normal file
25
frontend/src/components/ImageStudio/dashboard/types.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
export type ModuleStatus = 'live' | 'coming soon' | 'planning';
|
||||
|
||||
export type ModuleConfig = {
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
description: string;
|
||||
highlights: string[];
|
||||
status: ModuleStatus;
|
||||
route?: string;
|
||||
icon: React.ReactNode;
|
||||
help: string;
|
||||
pricing: {
|
||||
estimate: string;
|
||||
notes: string;
|
||||
};
|
||||
example: {
|
||||
title: string;
|
||||
steps: string[];
|
||||
eta: string;
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user