forked from 77media/video-flow
新增: 分享页规则; H5适配
This commit is contained in:
parent
8dbdee5b6c
commit
b98c34c39f
@ -197,36 +197,7 @@ export default function SharePage(): JSX.Element {
|
|||||||
<p data-alt="subtitle" className="mt-1 text-sm text-white/60">Invite friends to join and earn rewards.</p>
|
<p data-alt="subtitle" className="mt-1 text-sm text-white/60">Invite friends to join and earn rewards.</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
{/* Section 1: My Invitation Link */}
|
||||||
{/* Section 1: Invite Flow */}
|
|
||||||
<section data-alt="invite-flow" className="mb-8 rounded-lg border border-white/20 bg-black p-6 shadow-sm">
|
|
||||||
<h2 data-alt="section-title" className="text-lg font-medium text-white">Invitation Flow</h2>
|
|
||||||
<ol data-alt="steps" className="mt-4 grid gap-4 sm:grid-cols-3">
|
|
||||||
<li data-alt="step" className="rounded-md border border-white/20 p-4">
|
|
||||||
<div data-alt="step-header" className="flex items-center justify-between">
|
|
||||||
<span className="text-sm font-medium text-custom-blue/50">Step 1</span>
|
|
||||||
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Share</span>
|
|
||||||
</div>
|
|
||||||
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">Copy your invitation link and share it with friends.</p>
|
|
||||||
</li>
|
|
||||||
<li data-alt="step" className="rounded-md border border-white/20 p-4">
|
|
||||||
<div data-alt="step-header" className="flex items-center justify-between">
|
|
||||||
<span className="text-sm font-medium text-custom-blue/50">Step 2</span>
|
|
||||||
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Register</span>
|
|
||||||
</div>
|
|
||||||
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">Friends click the link and register directly.</p>
|
|
||||||
</li>
|
|
||||||
<li data-alt="step" className="rounded-md border border-white/20 p-4">
|
|
||||||
<div data-alt="step-header" className="flex items-center justify-between">
|
|
||||||
<span className="text-sm font-medium text-custom-blue/50">Step 3</span>
|
|
||||||
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Reward</span>
|
|
||||||
</div>
|
|
||||||
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">You both receive rewards after your friend activates their account.</p>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Section 2: My Invitation Link */}
|
|
||||||
<section data-alt="my-invite-link" className="mb-8 rounded-lg border border-white/20 bg-black p-6 shadow-sm">
|
<section data-alt="my-invite-link" className="mb-8 rounded-lg border border-white/20 bg-black p-6 shadow-sm">
|
||||||
<div data-alt="link-panel" className="mt-4 grid gap-6 sm:grid-cols-4">
|
<div data-alt="link-panel" className="mt-4 grid gap-6 sm:grid-cols-4">
|
||||||
<div data-alt="link-box" className="sm:col-span-2">
|
<div data-alt="link-box" className="sm:col-span-2">
|
||||||
@ -277,6 +248,74 @@ export default function SharePage(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Section 2: Invite Flow - Two Columns (Left: Steps, Right: Rules) */}
|
||||||
|
<section data-alt="invite-flow" className="mb-8 rounded-lg border border-white/20 bg-black p-6 shadow-sm">
|
||||||
|
<div data-alt="two-col-wrapper" className="mt-4 grid grid-cols-1 gap-6 md:grid-cols-[30%_1fr]">
|
||||||
|
{/* Left: Steps */}
|
||||||
|
<div data-alt="steps-col" className="space-y-4">
|
||||||
|
<h2 data-alt="section-title" className="text-lg font-medium text-white">Invitation Flow</h2>
|
||||||
|
<ol data-alt="steps" className="space-y-4">
|
||||||
|
<li data-alt="step" className="rounded-md p-4">
|
||||||
|
<div data-alt="step-header" className="flex items-center justify-between">
|
||||||
|
<span className="text-sm font-medium text-custom-blue/50">Step 1</span>
|
||||||
|
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Share</span>
|
||||||
|
</div>
|
||||||
|
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">Copy your invitation link and share it with friends.</p>
|
||||||
|
</li>
|
||||||
|
<li data-alt="step" className="rounded-md p-4">
|
||||||
|
<div data-alt="step-header" className="flex items-center justify-between">
|
||||||
|
<span className="text-sm font-medium text-custom-blue/50">Step 2</span>
|
||||||
|
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Register</span>
|
||||||
|
</div>
|
||||||
|
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">Friends click the link and register directly.</p>
|
||||||
|
</li>
|
||||||
|
<li data-alt="step" className="rounded-md p-4">
|
||||||
|
<div data-alt="step-header" className="flex items-center justify-between">
|
||||||
|
<span className="text-sm font-medium text-custom-blue/50">Step 3</span>
|
||||||
|
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Reward</span>
|
||||||
|
</div>
|
||||||
|
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">You both receive rewards after your friend activates their account.</p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
{/* Right: Rules */}
|
||||||
|
<div data-alt="rules-col" className="rounded-md">
|
||||||
|
<h2 data-alt="section-title" className="text-lg font-medium text-white mb-4">MovieFlow Credits Rewards Program</h2>
|
||||||
|
<div className='p-4 space-y-4'>
|
||||||
|
<p className="text-sm">Welcome to MovieFlow! Our Credits Program is designed to reward your growth and contributions. Credits can be redeemed for premium templates, effects, and membership time.</p>
|
||||||
|
|
||||||
|
<div className="content">
|
||||||
|
<h2 className="text-medium font-medium text-white">How to Earn Credits?</h2>
|
||||||
|
|
||||||
|
<div className="reward-section welcome">
|
||||||
|
<h3 className="text-medium font-medium text-white">Welcome Bonus</h3>
|
||||||
|
<p className='text-sm'>All <strong>new users</strong> receive a bonus of <span className="credit-amount">500 credits</span> upon successful registration!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="reward-section invite">
|
||||||
|
<h3 className="text-medium font-medium text-white">Invite & Earn</h3>
|
||||||
|
<p className='text-sm'>Invite friends to join using your unique referral link. Both you and your friend will get <span className="credit-amount">500 credits</span> once they successfully sign up.</p>
|
||||||
|
|
||||||
|
<div className="highlight">
|
||||||
|
<p className='text-sm'>If your invited friend completes their first purchase, you will receive a <strong>bonus equal to 20% of the credits</strong> they earn from that purchase.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="reward-section login">
|
||||||
|
<h3 className="text-medium font-medium text-white">Daily Login</h3>
|
||||||
|
<p className='text-sm'>Starting the day after registration, log in daily to claim <span className="credit-amount">100 credits</span>.</p>
|
||||||
|
<p className='text-sm'>This reward can be claimed for <strong>7 consecutive days</strong>.</p>
|
||||||
|
|
||||||
|
<div className="note">
|
||||||
|
<p className='text-sm'><strong>Please note:</strong> Daily login credits will <strong>reset</strong> automatically on the 8th day, so remember to use them in time!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
{/* Section 3: Invite Records */}
|
{/* Section 3: Invite Records */}
|
||||||
<section data-alt="invite-records" className="rounded-lg border border-white/20 bg-black p-6 shadow-sm">
|
<section data-alt="invite-records" className="rounded-lg border border-white/20 bg-black p-6 shadow-sm">
|
||||||
<div data-alt="section-header" className="mb-4 flex items-center justify-between">
|
<div data-alt="section-header" className="mb-4 flex items-center justify-between">
|
||||||
|
|||||||
@ -116,7 +116,7 @@ export default function SmartChatBox({
|
|||||||
const textBlock = last.blocks.find(b => (b as any).type === 'text') as any;
|
const textBlock = last.blocks.find(b => (b as any).type === 'text') as any;
|
||||||
const text = textBlock?.text || '';
|
const text = textBlock?.text || '';
|
||||||
if (text && onNewMessage) {
|
if (text && onNewMessage) {
|
||||||
const snippet = text.slice(0, 20);
|
const snippet = text.slice(0, 40);
|
||||||
onNewMessage(snippet);
|
onNewMessage(snippet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,7 +199,7 @@ export default function SmartChatBox({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Messages grouped by date */}
|
{/* Messages grouped by date */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3 pb-28">
|
||||||
{groupedMessages.map((group) => (
|
{groupedMessages.map((group) => (
|
||||||
<React.Fragment key={group.date}>
|
<React.Fragment key={group.date}>
|
||||||
<DateDivider timestamp={group.date} />
|
<DateDivider timestamp={group.date} />
|
||||||
|
|||||||
@ -624,7 +624,7 @@ Please process this video editing request.`;
|
|||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{(!isSmartChatBoxOpen && chatTip) && (
|
{(!isSmartChatBoxOpen && chatTip) && (
|
||||||
<div className="absolute -top-8 right-0 bg-black/80 text-white text-xs px-2 py-1 rounded-md whitespace-nowrap">
|
<div className="absolute -top-8 right-0 bg-black/80 text-white text-xs px-2 py-1 rounded-md whitespace-nowrap bg-custom-blue/30">
|
||||||
{chatTip}
|
{chatTip}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -640,7 +640,7 @@ Please process this video editing request.`;
|
|||||||
setChatTip(null);
|
setChatTip(null);
|
||||||
setHasUnread(false);
|
setHasUnread(false);
|
||||||
}}
|
}}
|
||||||
className="backdrop-blur-lg bg-custom-purple/80 border-transparent hover:opacity-90"
|
className="backdrop-blur-lg bg-custom-purple/80 border-transparent hover:bg-custom-purple/80"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@ -698,8 +698,8 @@ Please process this video editing request.`;
|
|||||||
if (!isSmartChatBoxOpen && snippet) {
|
if (!isSmartChatBoxOpen && snippet) {
|
||||||
setChatTip(snippet);
|
setChatTip(snippet);
|
||||||
setHasUnread(true);
|
setHasUnread(true);
|
||||||
// 3秒后自动消失
|
// 5秒后自动消失
|
||||||
setTimeout(() => setChatTip(null), 3000);
|
setTimeout(() => setChatTip(null), 5000);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClearPreview={() => {
|
onClearPreview={() => {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Carousel } from 'antd';
|
import { Carousel } from 'antd';
|
||||||
import type { CarouselRef } from 'antd/es/carousel';
|
import type { CarouselRef } from 'antd/es/carousel';
|
||||||
import { Play, Pause, Scissors, MessageCircleMore, Download, ArrowDownWideNarrow, RotateCcw, Navigation } from 'lucide-react';
|
import { Play, Pause, FeatherIcon, MessageCircleMore, Download, ArrowDownWideNarrow, RotateCcw, Navigation } from 'lucide-react';
|
||||||
import { TaskObject } from '@/api/DTO/movieEdit';
|
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||||
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
||||||
import ScriptLoading from './script-loading';
|
import ScriptLoading from './script-loading';
|
||||||
@ -395,15 +395,15 @@ export function H5MediaViewer({
|
|||||||
{stage === 'final_video' && videoUrls.length > 0 && renderVideoSlides()}
|
{stage === 'final_video' && videoUrls.length > 0 && renderVideoSlides()}
|
||||||
{stage === 'video' && videoUrls.length > 0 && renderVideoSlides()}
|
{stage === 'video' && videoUrls.length > 0 && renderVideoSlides()}
|
||||||
{(stage === 'scene' || stage === 'character') && imageUrls.length > 0 && renderImageSlides()}
|
{(stage === 'scene' || stage === 'character') && imageUrls.length > 0 && renderImageSlides()}
|
||||||
{/* 全局固定操作区(右上角)视频暂停时展示 */}
|
{/* 全局固定操作区(右下角)视频暂停时展示 */}
|
||||||
{(stage === 'video' || stage === 'final_video') && !isPlaying && (
|
{(stage === 'video' || stage === 'final_video') && !isPlaying && (
|
||||||
<div data-alt="global-video-actions" className="absolute top-2 right-6 z-[60] flex items-center gap-2">
|
<div data-alt="global-video-actions" className="absolute bottom-0 right-4 z-[60] flex flex-col items-center gap-2">
|
||||||
{stage === 'video' && (
|
{stage === 'video' && (
|
||||||
<>
|
<>
|
||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
data-alt="edit-with-chat-button"
|
data-alt="edit-with-chat-button"
|
||||||
className="w-8 h-8 bg-gradient-to-br from-blue-500/80 to-blue-600/80 backdrop-blur-xl border border-blue-400/30 rounded-full flex items-center justify-center hover:from-blue-400/80 hover:to-blue-500/80 transition-all"
|
className="w-8 h-8 bg-custom-purple backdrop-blur-xl rounded-full flex items-center justify-center transition-all"
|
||||||
icon={MessageCircleMore}
|
icon={FeatherIcon}
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label="edit-with-chat"
|
aria-label="edit-with-chat"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -416,7 +416,7 @@ export function H5MediaViewer({
|
|||||||
/>
|
/>
|
||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
data-alt="download-all-button"
|
data-alt="download-all-button"
|
||||||
className="w-8 h-8 bg-gradient-to-br from-purple-500/80 to-purple-600/80 backdrop-blur-xl border border-purple-400/30 rounded-full flex items-center justify-center hover:from-purple-400/80 hover:to-purple-500/80 transition-all"
|
className="w-8 h-8 bg-gradient-to-br from-purple-500/80 to-purple-600/80 backdrop-blur-xl rounded-full flex items-center justify-center hover:from-purple-400/80 hover:to-purple-500/80 transition-all"
|
||||||
icon={ArrowDownWideNarrow}
|
icon={ArrowDownWideNarrow}
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label="download-all"
|
aria-label="download-all"
|
||||||
@ -430,7 +430,7 @@ export function H5MediaViewer({
|
|||||||
return status === 1 ? (
|
return status === 1 ? (
|
||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
data-alt="download-current-button"
|
data-alt="download-current-button"
|
||||||
className="w-8 h-8 bg-gradient-to-br from-amber-500/80 to-yellow-600/80 backdrop-blur-xl border border-amber-400/30 rounded-full flex items-center justify-center hover:from-amber-400/80 hover:to-yellow-500/80 transition-all"
|
className="w-8 h-8 bg-gradient-to-br from-purple-600/80 to-purple-700/80 backdrop-blur-xl rounded-full flex items-center justify-center transition-all"
|
||||||
icon={Download}
|
icon={Download}
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label="download-current"
|
aria-label="download-current"
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React, { useEffect, useMemo, useState } from 'react'
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
import { TaskObject } from '@/api/DTO/movieEdit'
|
import { TaskObject } from '@/api/DTO/movieEdit'
|
||||||
import { motion } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
import { Heart, Camera, Film, Scissors, type LucideIcon } from 'lucide-react'
|
import { Heart, Camera, Film, Scissors, type LucideIcon } from 'lucide-react'
|
||||||
|
|
||||||
interface H5TaskInfoProps {
|
interface H5TaskInfoProps {
|
||||||
@ -94,6 +94,34 @@ const H5TaskInfo: React.FC<H5TaskInfoProps> = ({
|
|||||||
return null
|
return null
|
||||||
}, [selectedView, taskObject, displayCurrent, total])
|
}, [selectedView, taskObject, displayCurrent, total])
|
||||||
|
|
||||||
|
/** 阶段图标(H5 精简版) */
|
||||||
|
const StageIcon = useMemo(() => {
|
||||||
|
const Icon = stageIconMap[currentStage].icon
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
data-alt="stage-icon"
|
||||||
|
className="relative"
|
||||||
|
initial={{ opacity: 0, x: -8, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, x: 0, scale: 1 }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
className="rounded-full p-1"
|
||||||
|
animate={{
|
||||||
|
rotate: [0, 360],
|
||||||
|
scale: [1, 1.1, 1],
|
||||||
|
transition: {
|
||||||
|
rotate: { duration: 3, repeat: Infinity, ease: 'linear' },
|
||||||
|
scale: { duration: 1.6, repeat: Infinity, ease: 'easeInOut' }
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon className="w-4 h-4" style={{ color: stageColor }} />
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
}, [currentStage, stageColor])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-alt="h5-header"
|
data-alt="h5-header"
|
||||||
@ -101,9 +129,10 @@ const H5TaskInfo: React.FC<H5TaskInfoProps> = ({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-alt="h5-header-bar"
|
data-alt="h5-header-bar"
|
||||||
className="flex items-start justify-between"
|
className="flex items-start gap-3"
|
||||||
>
|
>
|
||||||
<div data-alt="title-area" className="flex flex-col min-w-0 bg-gradient-to-b from-slate-900/80 via-slate-900/40 to-transparent backdrop-blur-sm rounded-lg py-4">
|
{/* 左侧标题区域 */}
|
||||||
|
<div data-alt="title-area" className="flex-1 min-w-0 bg-gradient-to-b from-slate-900/80 via-slate-900/40 to-transparent backdrop-blur-sm rounded-lg py-4 px-4">
|
||||||
<h1
|
<h1
|
||||||
data-alt="title"
|
data-alt="title"
|
||||||
className="text-white text-lg font-bold"
|
className="text-white text-lg font-bold"
|
||||||
@ -116,7 +145,58 @@ const H5TaskInfo: React.FC<H5TaskInfoProps> = ({
|
|||||||
{subtitle}
|
{subtitle}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧状态区域 */}
|
||||||
|
<div data-alt="status-area" className="flex-shrink-0 bg-gradient-to-b from-slate-900/80 via-slate-900/40 to-transparent backdrop-blur-sm rounded-lg py-4 px-4 max-w-[200px]">
|
||||||
|
<AnimatePresence mode="popLayout">
|
||||||
|
{currentLoadingText && currentLoadingText !== 'Task completed' && (
|
||||||
|
<motion.div
|
||||||
|
key={currentLoadingText}
|
||||||
|
data-alt="status-line"
|
||||||
|
className="flex flex-col gap-2"
|
||||||
|
initial={{ opacity: 0, x: 10 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: 10 }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
{StageIcon}
|
||||||
|
</div>
|
||||||
|
<div className="relative text-center">
|
||||||
|
{/* 背景流光 */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-cyan-400 to-purple-400 blur-[1px]"
|
||||||
|
animate={{
|
||||||
|
backgroundPosition: ['0% 50%', '100% 50%', '0% 50%'],
|
||||||
|
transition: { duration: 2, repeat: Infinity, ease: 'linear' }
|
||||||
|
}}
|
||||||
|
style={{ backgroundSize: '200% 200%' }}
|
||||||
|
>
|
||||||
|
<span className="text-xs leading-tight break-words">{currentLoadingText}</span>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* 主文字轻微律动 */}
|
||||||
|
<motion.div
|
||||||
|
className="relative z-10"
|
||||||
|
animate={{ scale: [1, 1.02, 1] }}
|
||||||
|
transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut' }}
|
||||||
|
>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* 底部装饰线 */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 h-0.5 w-8"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(to right, ${stageColor}, rgb(34 211 238), rgb(168 85 247))`
|
||||||
|
}}
|
||||||
|
animate={{ width: ['0%', '100%', '0%'] }}
|
||||||
|
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user