forked from 77media/video-flow
615 lines
21 KiB
TypeScript
615 lines
21 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useRef, useEffect, useMemo } from "react";
|
||
import {
|
||
Table,
|
||
AlignHorizontalSpaceAround,
|
||
Loader2,
|
||
Clapperboard,
|
||
CircleArrowRight,
|
||
} from "lucide-react";
|
||
import "./style/home-page2.css";
|
||
import { usePathname, useRouter } from "next/navigation";
|
||
import { Swiper, SwiperSlide } from "swiper/react";
|
||
import "swiper/swiper-bundle.css"; // 引入样式
|
||
import { Autoplay } from "swiper/modules";
|
||
|
||
import { VideoCarouselLayout } from "@/components/video-carousel-layout";
|
||
import { VideoGridLayout } from "@/components/video-grid-layout";
|
||
import { motion, AnimatePresence } from "framer-motion";
|
||
import { LiquidButton } from "@/components/ui/liquid-glass-button";
|
||
import {
|
||
createScriptProject,
|
||
CreateScriptProjectRequest,
|
||
} from "@/api/script_project";
|
||
import { ProjectTypeEnum, ModeEnum, ResolutionEnum } from "@/app/model/enums";
|
||
import { getResourcesList, Resource } from "@/api/resources";
|
||
import { Carousel } from "antd";
|
||
import { TextCanvas } from "../common/TextCanvas";
|
||
import { fetchSubscriptionPlans, SubscriptionPlan } from "@/lib/stripe";
|
||
import { useCallbackModal } from "@/app/layout";
|
||
|
||
export function HomePage2() {
|
||
const [hPading, setHPading] = useState(0);
|
||
useEffect(() => {
|
||
// 获取当前窗口尺寸
|
||
const currentWidth = window.innerWidth;
|
||
const currentHeight = window.innerHeight;
|
||
// 计算缩放比例 (1920x1080)
|
||
const wScale = currentWidth / 1920;
|
||
const hScale = currentHeight / 1080;
|
||
|
||
// 检查app节点是否存在
|
||
const homePage = document.getElementById("home-page");
|
||
if (!homePage) {
|
||
console.error("未找到app节点");
|
||
return;
|
||
}
|
||
// setHPading((hScale || 1) * 10);
|
||
// 创建样式元素
|
||
const style = document.createElement("style");
|
||
|
||
// 设置CSS样式
|
||
style.textContent = `
|
||
#home-page {
|
||
transform-origin: top left;
|
||
transform: scale(${wScale}, ${hScale});
|
||
width: 1920px;
|
||
height: 1080px;
|
||
}
|
||
`;
|
||
|
||
// 将样式添加到head
|
||
document.head.appendChild(style);
|
||
}, []);
|
||
return (
|
||
//
|
||
<div
|
||
className="w-full h-screen overflow-y-auto"
|
||
id="home-page"
|
||
style={{ paddingBottom: `2rem` }}
|
||
>
|
||
<HomeModule1 />
|
||
<HomeModule2 />
|
||
<HomeModule3 />
|
||
<HomeModule4 />
|
||
<HomeModule5 />
|
||
<HomeModule6 />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/** 首屏 */
|
||
function HomeModule1() {
|
||
const router = useRouter();
|
||
return (
|
||
<div className="home-module1 relative flex justify-center items-start pt-[28rem] w-full h-[1280px] bg-black snap-start">
|
||
<video
|
||
src="https://cdn.qikongjian.com/videos/home.mp4"
|
||
autoPlay
|
||
loop
|
||
muted
|
||
playsInline
|
||
className="absolute top-0 left-0 z-1 w-full h-full object-cover"
|
||
></video>
|
||
<div className="center z-10 flex flex-col items-center">
|
||
<h1 className="text-white text-[5.75rem] leading-[100%] font-bold mb-[1rem]">
|
||
Ideas Become Movies
|
||
</h1>
|
||
<p className="text-white text-[2rem] leading-[140%] font-normal">
|
||
One line, one film—your story, your scene.
|
||
</p>
|
||
<p className="text-white text-[2rem] leading-[140%] font-normal">
|
||
Everyone is a movie master.
|
||
</p>
|
||
<div
|
||
className="w-[11.5rem] h-[3.75rem] mt-[4rem] text-base flex justify-center items-center font-normal border border-white rounded-full bg-white/30 cursor-pointer"
|
||
onClick={() => router.push("/create")}
|
||
>
|
||
Early Access
|
||
<CircleArrowRight className="ml-[1rem]" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
/**核心价值 */
|
||
function HomeModule2() {
|
||
const videoList = [
|
||
{
|
||
title: "Text to Movie",
|
||
video: "https://cdn.qikongjian.com/videos/module2 (1).mp4",
|
||
},
|
||
{
|
||
title: "Image to Movie",
|
||
video: "https://cdn.qikongjian.com/videos/module2 (2).mp4",
|
||
},
|
||
{
|
||
title: "Template to Movie",
|
||
video: "https://cdn.qikongjian.com/videos/module2 (3).mp4",
|
||
},
|
||
];
|
||
return (
|
||
<div
|
||
data-alt="core-value-section"
|
||
className="home-module2 relative flex flex-col items-center justify-center w-full h-[1300px] bg-black snap-start"
|
||
>
|
||
<div
|
||
data-alt="core-value-content"
|
||
className="center z-10 flex flex-col items-center mb-[8rem]"
|
||
>
|
||
<h2 className="text-white text-[3.375rem] leading-[100%] font-normal mb-[3rem]">
|
||
Just Give Us Your Ideas
|
||
</h2>
|
||
<p className="text-white text-[1.125rem] leading-[140%] font-normal text-center">
|
||
Input your idea in one sentence and MovieFlow will bring
|
||
</p>
|
||
<p className="text-white text-[1.125rem] leading-[140%] font-normal text-center">
|
||
your creativity to life
|
||
</p>
|
||
</div>
|
||
<div
|
||
data-alt="core-value-videos"
|
||
className="flex justify-center gap-[1rem] w-full px-[4rem]"
|
||
>
|
||
{/* 第一个视频 */}
|
||
{videoList.map((item, index) => (
|
||
<div
|
||
data-alt="core-value-video-1"
|
||
className="flex flex-col items-center"
|
||
key={index}
|
||
>
|
||
<video
|
||
src={item.video}
|
||
autoPlay
|
||
loop
|
||
muted
|
||
playsInline
|
||
className=" h-[20rem] object-cover border border-white/20 rounded-lg"
|
||
/>
|
||
<h3 className="mt-[1rem] text-white text-[1.5rem] font-medium">
|
||
{item.title}
|
||
</h3>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
/**案例展示 */
|
||
function HomeModule3() {
|
||
const videoList = [
|
||
[
|
||
"https://cdn.qikongjian.com/videos/show (1).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (2).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (3).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (4).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (5).mp4",
|
||
],
|
||
[
|
||
"https://cdn.qikongjian.com/videos/show (6).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (7).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (8).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (9).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (10).mp4",
|
||
],
|
||
[
|
||
"https://cdn.qikongjian.com/videos/show (11).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (12).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (13).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (14).mp4",
|
||
"https://cdn.qikongjian.com/videos/show (15).mp4",
|
||
],
|
||
];
|
||
|
||
// 定义内容样式
|
||
const contentStyle: React.CSSProperties = {
|
||
height: "100%",
|
||
lineHeight: "10rem",
|
||
textAlign: "center",
|
||
background: "#364d79",
|
||
};
|
||
|
||
return (
|
||
<div className="home-module3 h-[1360px] relative flex flex-col items-center justify-center w-full bg-black snap-start">
|
||
<div className="center z-10 flex flex-col items-center mb-[4rem]">
|
||
<h2 className="text-white text-[3.375rem] leading-[100%] font-normal mb-[3rem]">
|
||
Create Anything
|
||
</h2>
|
||
<p className="text-white text-[1.125rem] leading-[140%] font-normal text-center">
|
||
MovieFlow can make any kind of film in high quality for you
|
||
</p>
|
||
</div>
|
||
{/* 3x3网格布局 */}
|
||
<div
|
||
data-alt="vertical-grid-shadow"
|
||
className="grid grid-cols-3 gap-[1rem] w-full h-[64rem] px-[5rem] relative"
|
||
>
|
||
{/* 上方阴影遮罩 - 使用 mask 实现真正的渐变模糊,加重黑色 */}
|
||
<div
|
||
className="absolute -top-[1rem] -left-0 w-full h-[20rem] z-20 pointer-events-none"
|
||
style={{
|
||
backdropFilter: "blur(12px)",
|
||
WebkitBackdropFilter: "blur(12px)",
|
||
backgroundColor: "rgba(0,0,0,0.9)",
|
||
mask: "linear-gradient(to bottom, black 0%, black 30%, rgba(0,0,0,0.9) 50%, rgba(0,0,0,0.6) 75%, transparent 100%)",
|
||
WebkitMask:
|
||
"linear-gradient(to bottom, black 0%, black 30%, rgba(0,0,0,0.9) 50%, rgba(0,0,0,0.6) 75%, transparent 100%)",
|
||
}}
|
||
></div>
|
||
|
||
{/* 下方阴影遮罩 - 使用 mask 实现真正的渐变模糊,加重黑色 */}
|
||
<div
|
||
className="absolute -bottom-[1rem] -left-0 w-full h-[20rem] z-20 pointer-events-none"
|
||
style={{
|
||
backdropFilter: "blur(12px)",
|
||
WebkitBackdropFilter: "blur(12px)",
|
||
backgroundColor: "rgba(0,0,0,0.9)",
|
||
mask: "linear-gradient(to top, black 0%, black 30%, rgba(0,0,0,0.9) 50%, rgba(0,0,0,0.6) 75%, transparent 100%)",
|
||
WebkitMask:
|
||
"linear-gradient(to top, black 0%, black 20%, rgba(0,0,0,0.9) 50%, rgba(0,0,0,0.6) 75%, transparent 100%)",
|
||
}}
|
||
></div>
|
||
{videoList.map((column, columnIndex) => (
|
||
<div
|
||
key={columnIndex}
|
||
className="w-[35.5rem] h-[64rem] relative z-10"
|
||
>
|
||
<Swiper
|
||
modules={[Autoplay]}
|
||
direction="vertical"
|
||
loop={true}
|
||
spaceBetween={0}
|
||
slidesPerView={3}
|
||
autoplay={{
|
||
delay: 0,
|
||
pauseOnMouseEnter: true,
|
||
disableOnInteraction: false,
|
||
reverseDirection: columnIndex % 2 === 0,
|
||
}}
|
||
speed={6000}
|
||
grabCursor={true}
|
||
className="w-full h-full"
|
||
cssMode={false}
|
||
freeMode={true}
|
||
watchSlidesProgress={false}
|
||
style={
|
||
{
|
||
"--swiper-wrapper-transition-timing-function": "linear",
|
||
} as React.CSSProperties
|
||
}
|
||
>
|
||
{column.map((video, videoIndex) => (
|
||
<SwiperSlide key={videoIndex}>
|
||
<div className="w-full h-[20rem] bg-gray-800 rounded-2xl overflow-hidden">
|
||
<video
|
||
src={video}
|
||
loop
|
||
muted
|
||
playsInline
|
||
className="w-full h-full object-cover"
|
||
onMouseEnter={(e) => {
|
||
const videoElement = e.currentTarget;
|
||
videoElement.play();
|
||
}}
|
||
/>
|
||
</div>
|
||
</SwiperSlide>
|
||
))}
|
||
</Swiper>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
/**电影制作工序介绍 */
|
||
function HomeModule4() {
|
||
const [activeTab, setActiveTab] = useState(0);
|
||
|
||
const processSteps = [
|
||
{
|
||
title: "The Narrative Engine",
|
||
description:
|
||
" From a single thought, it builds entire worlds and compelling plots.",
|
||
video: "https://cdn.qikongjian.com/videos/module4 (3).mp4",
|
||
},
|
||
{
|
||
title: "AI Character Engine",
|
||
description:
|
||
"Cast your virtual actors. Lock them in once, for the entire story.",
|
||
video: "https://cdn.qikongjian.com/videos/module4 (1).mp4",
|
||
},
|
||
{
|
||
title: "AI vision engine",
|
||
description:
|
||
"It translates your aesthetic into art, light, and cinematography for every single shot.",
|
||
video: "https://cdn.qikongjian.com/videos/module4 (4).mp4",
|
||
},
|
||
{
|
||
title: "Intelligent Editing Engine",
|
||
description:
|
||
"An editing AI drives the final cut, for a story told seamlessly.",
|
||
video: "https://cdn.qikongjian.com/videos/module4 (2).mp4",
|
||
},
|
||
];
|
||
|
||
const handleTabClick = (index: number) => {
|
||
setActiveTab(index);
|
||
};
|
||
|
||
return (
|
||
<div
|
||
data-alt="core-value-section"
|
||
className="home-module4 h-[1280px] relative flex flex-col items-center justify-center w-full bg-black snap-start"
|
||
>
|
||
<div
|
||
data-alt="core-value-content"
|
||
className="center z-10 flex flex-col items-center mb-[14rem]"
|
||
>
|
||
<h2 className="text-white text-[3.375rem] leading-[100%] font-normal ">
|
||
Edit like you think
|
||
</h2>
|
||
</div>
|
||
|
||
<div className="flex w-full px-[4rem] gap-[2rem]">
|
||
{/* 左侧四个切换tab */}
|
||
<div className="flex flex-col gap-[1rem]">
|
||
{processSteps.map((step, index) => (
|
||
<div
|
||
key={index}
|
||
onClick={() => handleTabClick(index)}
|
||
className={`w-[31.75rem] h-[10.5rem] rounded-lg cursor-pointer transition-all duration-300 border ${
|
||
activeTab === index
|
||
? "bg-[#262626] border-white/20 hover:border-white/40"
|
||
: "bg-black border-white/10 hover:border-white/40"
|
||
}`}
|
||
>
|
||
<div className="p-[1.5rem] h-full flex flex-col justify-center">
|
||
<h3
|
||
className={`text-[2rem] font-normal mb-[1.5rem] ${
|
||
activeTab === index ? "text-white" : "text-white/70"
|
||
}`}
|
||
>
|
||
{step.title}
|
||
</h3>
|
||
<p
|
||
className={`text-[0.875rem] leading-[140%] ${
|
||
activeTab === index ? "text-white" : "text-white/70"
|
||
}`}
|
||
>
|
||
{step.description}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 右侧视频播放区域 */}
|
||
<div className="flex-1 flex justify-center">
|
||
<div className="w-[80rem] h-[45rem] bg-gray-800 rounded-lg overflow-hidden border border-white/20">
|
||
<video
|
||
key={activeTab}
|
||
src={processSteps[activeTab].video}
|
||
autoPlay
|
||
loop
|
||
muted
|
||
playsInline
|
||
className="w-full h-full object-cover"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
/**价格方案 */
|
||
function HomeModule5() {
|
||
const [billingType, setBillingType] = useState<"month" | "year">("month");
|
||
|
||
const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
|
||
const { setShowCallbackModal } = useCallbackModal();
|
||
const pathname = usePathname();
|
||
// 从后端获取订阅计划数据
|
||
useEffect(() => {
|
||
const loadPlans = async () => {
|
||
try {
|
||
const plansData = await fetchSubscriptionPlans();
|
||
setPlans(plansData);
|
||
} catch (err) {
|
||
console.error("加载订阅计划失败:", err);
|
||
}
|
||
};
|
||
|
||
loadPlans();
|
||
}, []);
|
||
|
||
const pricingPlans = useMemo<
|
||
{
|
||
title: string;
|
||
price: number;
|
||
credits: string;
|
||
buttonText: string;
|
||
features: string[];
|
||
}[]
|
||
>(() => {
|
||
return plans.map((plan) => {
|
||
return {
|
||
title: plan.display_name || plan.name,
|
||
price:
|
||
billingType === "month"
|
||
? plan.price_month / 100
|
||
: plan.price_year / 100,
|
||
credits: plan.description,
|
||
buttonText: plan.is_free ? "Try For Free" : "Subscribe Now",
|
||
features: plan.features || [],
|
||
};
|
||
});
|
||
}, [plans, billingType]);
|
||
|
||
const handleSubscribe = async (planName: string) => {
|
||
if (planName === "hobby") {
|
||
return;
|
||
}
|
||
localStorage.setItem("callBackUrl", pathname);
|
||
try {
|
||
// 使用新的Checkout Session方案(更简单!)
|
||
const { createCheckoutSession, redirectToCheckout } = await import(
|
||
"@/lib/stripe"
|
||
);
|
||
|
||
// 从localStorage获取当前用户信息
|
||
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
|
||
|
||
if (!User.id) {
|
||
throw new Error("无法获取用户ID,请重新登录");
|
||
}
|
||
|
||
// 1. 创建Checkout Session
|
||
const result = await createCheckoutSession({
|
||
user_id: String(User.id),
|
||
plan_name: planName,
|
||
billing_cycle: billingType,
|
||
});
|
||
|
||
if (!result.successful || !result.data) {
|
||
throw new Error("create checkout session failed");
|
||
}
|
||
|
||
setShowCallbackModal(true);
|
||
window.open(result.data.checkout_url, "_blank");
|
||
} catch (error) {
|
||
throw new Error("create checkout session failed, please try again later");
|
||
}
|
||
};
|
||
return (
|
||
<div
|
||
data-alt="core-value-section"
|
||
className="home-module5 h-[1600px] relative flex flex-col items-center justify-center w-full bg-black snap-start"
|
||
>
|
||
<div
|
||
data-alt="core-value-content"
|
||
className="center z-10 flex flex-col items-center mb-[8rem]"
|
||
>
|
||
<h2 className="text-white text-[3.375rem] leading-[100%] font-normal mb-[1.5rem]">
|
||
Start Creating
|
||
</h2>
|
||
|
||
{/* 计费切换 */}
|
||
<div className="h-[3.375rem] flex bg-black rounded-full p-[0.0625rem] mt-[1.5rem] border border-white/20">
|
||
<button
|
||
onClick={() => setBillingType("month")}
|
||
className={`box-border flex justify-center items-center w-[6rem] text-base rounded-full transition-all duration-300 ${
|
||
billingType === "month"
|
||
? "bg-white text-black"
|
||
: "text-white hover:text-gray-300"
|
||
}`}
|
||
>
|
||
Monthly
|
||
</button>
|
||
<button
|
||
onClick={() => setBillingType("year")}
|
||
className={`box-border flex justify-center items-center w-[7.125rem] text-base rounded-full transition-all duration-300 ${
|
||
billingType === "year"
|
||
? "bg-white text-black"
|
||
: "text-white hover:text-gray-300"
|
||
}`}
|
||
>
|
||
Yearly - <span className="text-[#FFCC6D]">10%</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 主要价格卡片 */}
|
||
<div className="flex justify-between w-[90%] px-[12rem] mb-[2rem]">
|
||
{pricingPlans.map((plan, index) => (
|
||
<div
|
||
key={index}
|
||
className=" w-[24rem] h-[38.125rem] bg-black rounded-lg p-[1.5rem] border border-white/20"
|
||
>
|
||
<h3 className="text-white text-2xl font-normal mb-[1rem]">
|
||
{plan.title}
|
||
</h3>
|
||
<div className="mb-[1rem]">
|
||
<span className="text-white text-[3.375rem] font-bold">
|
||
${plan.price}
|
||
</span>
|
||
<span className="text-white text-xs ml-[0.5rem]">/month</span>
|
||
</div>
|
||
<p className="text-white text-[0.875rem] mb-[1rem]">
|
||
{plan.credits}
|
||
</p>
|
||
<button
|
||
onClick={() => handleSubscribe(plan.title)}
|
||
className="w-full bg-white text-black py-[0.75rem] rounded-full mb-[1rem] hover:bg-black hover:text-white transition-colors border border-white/20"
|
||
>
|
||
{plan.buttonText}
|
||
</button>
|
||
<p className="w-full text-center text-white/60 text-[0.75rem] mb-[2rem]">
|
||
* Billed monthly until cancelled
|
||
</p>
|
||
<ul className="space-y-[1rem]">
|
||
{plan.features.map((feature, featureIndex) => (
|
||
<li
|
||
key={featureIndex}
|
||
className="flex items-center text-white text-[0.875rem]"
|
||
>
|
||
<span className="text-[#C73BFF] mr-[0.5rem]">✓</span>
|
||
{feature}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 额外价格卡片 */}
|
||
<div className="flex gap-[1.5rem] w-[90%] px-[12rem]">
|
||
<div className="flex-1 bg-black rounded-lg p-[1.5rem] border border-white/20">
|
||
<h3 className="text-white text-[1.5rem] font-bold mb-[0.5rem]">
|
||
Free
|
||
</h3>
|
||
<div className="mb-[1rem]">
|
||
<span className="text-white text-[2.5rem] font-bold">$0</span>
|
||
</div>
|
||
<p className="text-white text-[0.875rem] mb-[1.5rem] leading-relaxed">
|
||
10 Video mins and 1 AI credit per week, 1 Express avatar, 4 Exports
|
||
per week with invideo watermark.
|
||
</p>
|
||
<p className="text-white text-[0.875rem] mb-[1.5rem] leading-relaxed">
|
||
No access to generative features.
|
||
</p>
|
||
<button className="w-[9rem] bg-[#262626] text-white py-[0.75rem] rounded-full hover:bg-white hover:text-black transition-colors border border-white/20">
|
||
Try For Free
|
||
</button>
|
||
</div>
|
||
|
||
<div className="flex-1 bg-black rounded-lg p-[1.5rem] border border-white/20">
|
||
<h3 className="text-white text-[1.5rem] font-bold mb-[0.5rem]">
|
||
Enterprise
|
||
</h3>
|
||
<p className="text-white text-[2.5rem] mb-[1rem]">Custom</p>
|
||
<p className="text-white text-[0.875rem] mb-[1.5rem] leading-relaxed">
|
||
Custom solutions for large organizations. Advanced security and
|
||
flexible pricing based on your needs.
|
||
</p>
|
||
<p className="text-white text-[0.875rem] mb-[1.5rem] leading-relaxed">
|
||
on your needs.
|
||
</p>
|
||
<button className="w-[9rem] bg-[#262626] text-white py-[0.75rem] rounded-full hover:bg-white hover:text-black transition-colors border border-white/20">
|
||
Contact Us
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function HomeModule6() {
|
||
return (
|
||
<div className="home-module6 flex justify-center items-center w-full h-min text-white/50 text-lg bg-black snap-start">
|
||
© 2025 MovieFlow. All rights reserved.
|
||
</div>
|
||
);
|
||
}
|