video-flow-b/components/pages/home-page2.tsx
2025-09-29 12:06:20 +08:00

1605 lines
62 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useRef, useEffect, useMemo } from "react";
import "./style/home-page2.css";
import { usePathname, useRouter } from "next/navigation";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/swiper-bundle.css"; // 引入样式
import "swiper/css/effect-coverflow";
import "swiper/css/effect-cube";
import "swiper/css/pagination";
import {
Autoplay,
EffectCoverflow,
EffectCube,
Pagination,
} from "swiper/modules";
import LazyLoad from "react-lazyload";
import { fetchSubscriptionPlans, SubscriptionPlan } from "@/lib/stripe";
import VideoCoverflow from "@/components/ui/VideoCoverflow";
import { fetchTabsByCode, HomeTabItem } from "@/api/serversetting";
import H5TopBar from "@/components/layout/H5TopBar";
import { useCallbackModal } from "@/app/layout";
import { useDeviceType } from "@/hooks/useDeviceType";
import Footer from "@/components/common/Footer";
/** 视频预加载系统 - 后台静默运行 */
function useVideoPreloader() {
/** 预加载单个视频 */
const preloadVideo = (src: string): Promise<void> => {
return new Promise((resolve) => {
const video = document.createElement("video");
video.muted = true;
video.preload = "auto";
// 设置超时,避免某个视频卡住整个预加载过程
const timeout = setTimeout(() => {
console.warn(`视频预加载超时: ${src}`);
resolve();
}, 10000); // 10秒超时
video.onloadeddata = () => {
clearTimeout(timeout);
resolve();
};
video.onerror = () => {
clearTimeout(timeout);
console.warn(`视频预加载失败: ${src}`);
resolve(); // 即使失败也继续,不影响其他视频
};
video.src = src;
video.load();
});
};
/** 预加载所有视频 */
const preloadAllVideos = async () => {
const allVideos = [
// HomeModule1 - 首屏视频(最高优先级)
"https://cdn.qikongjian.com/videos/home.mp4",
// HomeModule2 - 核心价值视频(高优先级)
"https://cdn.qikongjian.com/videos/module2 (1).mp4",
"https://cdn.qikongjian.com/videos/module2 (2).mp4",
"https://cdn.qikongjian.com/videos/module2 (3).mp4",
// HomeModule4 - 制作工序视频(中优先级)
"https://cdn.qikongjian.com/videos/module4 (3).mp4",
"https://cdn.qikongjian.com/videos/module4 (1).mp4",
"https://cdn.qikongjian.com/videos/module4 (4).mp4",
"https://cdn.qikongjian.com/videos/module4 (2).mp4",
// HomeModule3 - 案例展示视频(低优先级,数量多)
"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",
];
try {
// 分阶段预加载:先加载关键视频,再加载其他视频
const criticalVideos = allVideos.slice(0, 8); // 前8个是关键视频
const otherVideos = allVideos.slice(8);
// 第一阶段:预加载关键视频
for (let i = 0; i < criticalVideos.length; i += 2) {
const batch = criticalVideos.slice(i, i + 2);
await Promise.all(batch.map(preloadVideo));
}
// 第二阶段:后台预加载其他视频
if (otherVideos.length > 0) {
// 使用 requestIdleCallback 在浏览器空闲时预加载
const preloadRemaining = () => {
let index = 0;
const processBatch = () => {
if (index >= otherVideos.length) return;
const batch = otherVideos.slice(
index,
Math.min(index + 3, otherVideos.length)
);
batch.forEach(preloadVideo);
index += batch.length;
if (index < otherVideos.length) {
requestIdleCallback(processBatch, { timeout: 1000 });
}
};
requestIdleCallback(processBatch, { timeout: 1000 });
};
// 如果浏览器不支持 requestIdleCallback使用 setTimeout
if (typeof requestIdleCallback !== "undefined") {
preloadRemaining();
} else {
setTimeout(() => {
for (let i = 0; i < otherVideos.length; i += 3) {
const batch = otherVideos.slice(i, i + 3);
batch.forEach(preloadVideo);
}
}, 100);
}
}
} catch (error) {
console.error("视频预加载过程中出现错误:", error);
}
};
// useEffect(() => {
// // 使用 requestIdleCallback 在浏览器空闲时开始预加载
// // 如果浏览器不支持,则使用 setTimeout 延迟执行
// if (typeof requestIdleCallback !== "undefined") {
// requestIdleCallback(() => preloadAllVideos(), { timeout: 2000 });
// } else {
// setTimeout(() => preloadAllVideos(), 100);
// }
// }, []);
// 这个 hook 不需要返回任何值,它只是后台静默运行
return;
}
export function HomePage2() {
// 后台静默预加载视频,不显示任何加载界面
useVideoPreloader();
/** 导航滚动容器与各锚点 */
const containerRef = useRef<HTMLDivElement | null>(null);
const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({
home: null,
core: null,
cases: null,
showreel: null,
process: null,
pricing: null,
footer: null,
});
const [menuOpen, setMenuOpen] = useState(false);
const [homeTabsPC, setHomeTabsPC] = useState<HomeTabItem[]>([]);
const [homeTabsH5, setHomeTabsH5] = useState<HomeTabItem[]>([]);
const { isMobile } = useDeviceType();
// 旧锚点兼容已移除,完全使用接口 homeTab 渲染
useEffect(() => {
let isMounted = true;
async function loadTabsBoth() {
try {
const [pc, h5] = await Promise.all([
fetchTabsByCode('homeTab'),
fetchTabsByCode('homeTabH5'),
]);
if (!isMounted) return;
if (Array.isArray(pc)) setHomeTabsPC(pc);
if (Array.isArray(h5)) setHomeTabsH5(h5);
} catch {
// 忽略错误,保持空态
}
}
loadTabsBoth();
return () => {
isMounted = false;
};
}, []);
const tabsToRender = isMobile ? homeTabsH5 : homeTabsPC;
/** 在容器内平滑滚动到锚点,兼容有固定导航时的微调 */
const scrollToSection = (key: keyof typeof sectionRefs.current) => {
try {
const container = containerRef.current;
const target = sectionRefs.current[key];
if (!container || !target) return;
const containerTop = container.getBoundingClientRect().top;
const targetTop = target.getBoundingClientRect().top;
const currentScrollTop = container.scrollTop;
const NAV_OFFSET = 64; // 顶部导航高度微调
const delta = targetTop - containerTop + currentScrollTop - NAV_OFFSET;
container.scrollTo({ top: Math.max(delta, 0), behavior: 'smooth' });
setMenuOpen(false);
} catch {
// 忽略滚动异常
}
};
const NavBar = () => {
if (tabsToRender.length === 0) return null;
useEffect(() => {
const handler = () => setMenuOpen((v) => !v);
window.addEventListener('home-menu-toggle' as any, handler as any);
return () => window.removeEventListener('home-menu-toggle' as any, handler as any);
}, []);
return (
<div data-alt="home-navbar" className="fixed h-16 top-0 left-0 right-0 z-[19]">
<div className="mx-auto h-full">
<div className="flex h-full items-center justify-center px-4 sm:px-6 py-3 bg-black/60 backdrop-blur-md border-b border-white/10">
{/* 桌面端菜单(居中,仅三个项) */}
<div data-alt="desktop-menu" className="hidden md:flex items-center gap-6 text-white/90 text-sm h-full">
{tabsToRender.map((tab) => (
<button
key={tab.title}
data-alt={`nav-${tab.title.toLowerCase()}`}
className="hover:text-white"
onClick={() => scrollToSection(tab.title.toLowerCase() as any)}
>
{tab.title}
</button>
))}
</div>
{/* 移动端交由 H5TopBar 控制 */}
<span className="md:hidden" data-alt="mobile-menu-toggle-placeholder"></span>
</div>
</div>
</div>
);
};
return (
<div className="w-full h-screen overflow-y-auto" id="home-page" ref={containerRef} style={{ paddingBottom: `2rem` }}>
{/* 移动端顶部导航(抽屉式) */}
{ isMobile ? (<H5TopBar onSelectHomeTab={(key) => scrollToSection(key as any)} />) : (<NavBar />) }
<HomeModule1 />
<LazyLoad once>
<HomeModule2 />
</LazyLoad>
<LazyLoad once>
<HomeModule3 />
</LazyLoad>
{/* 动态锚点:来源于服务端 homeTab/homeTabH5 配置title 作为锚点与标题 */}
{tabsToRender.map((tab) => (
<div key={tab.title} data-alt={`anchor-${tab.title.toLowerCase()}`} ref={(el) => (sectionRefs.current as any)[tab.title.toLowerCase()] = el}>
<VideoCoverflow title={tab.title} subtitle={tab.subtitle} videos={tab.videos} />
</div>
))}
<LazyLoad once>
<HomeModule4 />
</LazyLoad>
<HomeModule5 />
<Footer showEmailLink={true} />
</div>
);
}
/** 首屏 */
function HomeModule1() {
const router = useRouter();
return (
<div
className="home-module1 relative flex justify-center items-start w-full bg-black snap-start
/* 移动端适配 */
h-[100vh] pt-[40vh]
/* 平板适配 */
sm:h-[100vh] sm:pt-[35vh]
/* 小屏笔记本适配 (13-15寸) */
md:h-[100vh] md:pt-[30vh]
/* 大屏笔记本适配 (16-17寸) */
lg:h-[100vh] lg:pt-[28vh]
/* 桌面端适配 (21-24寸) */
xl:h-[100vh] xl:pt-[25vh]
/* 大屏显示器适配 (27寸+) */
2xl:h-[100vh] 2xl:pt-[22vh]"
>
<LazyLoad once>
<video
src="https://cdn.qikongjian.com/videos/1759064057657_cu0po2.mp4"
poster="https://cdn.qikongjian.com/videos/1759064057657_cu0po2.mp4?vframe/jpg/offset/1"
autoPlay
loop
muted
playsInline
preload="none"
className="absolute top-0 left-0 z-1 w-full h-full
/* 移动端优化 */
object-cover
/* 平板及以上优化 */
sm:object-cover
/* 桌面端优化 */
md:object-cover lg:object-cover xl:object-cover 2xl:object-cover"
></video>
</LazyLoad>
<div className="center z-10 flex flex-col items-center px-4">
<h1
className="text-white font-bold text-center
/* 移动端字体 */
text-[2.5rem] leading-[110%] mb-4
/* 平板字体 */
sm:text-[3.5rem] sm:leading-[110%] sm:mb-6
/* 小屏笔记本字体 */
md:text-[4.5rem] md:leading-[110%] md:mb-8
/* 大屏笔记本字体 */
lg:text-[5rem] lg:leading-[110%] lg:mb-8
/* 桌面端字体 */
xl:text-[5.5rem] xl:leading-[110%] xl:mb-10
/* 大屏显示器字体 */
2xl:text-[6rem] 2xl:leading-[110%] 2xl:mb-12"
>
Ideas Spark Movies
</h1>
<p
className="text-white font-normal text-center
/* 移动端字体 */
text-[1rem] leading-[140%] mb-2
/* 平板字体 */
sm:text-[1.25rem] sm:leading-[140%] sm:mb-3
/* 小屏笔记本字体 */
md:text-[1.5rem] md:leading-[140%] md:mb-4
/* 大屏笔记本字体 */
lg:text-[1.75rem] lg:leading-[140%] lg:mb-4
/* 桌面端字体 */
xl:text-[2rem] xl:leading-[140%] xl:mb-5
/* 大屏显示器字体 */
2xl:text-[2.25rem] 2xl:leading-[140%] 2xl:mb-6"
>
One line, one filmyour story, your scene.
</p>
<p
className="text-white font-normal text-center
/* 移动端字体 */
text-[1rem] leading-[140%] mb-6
/* 平板字体 */
sm:text-[1.25rem] sm:leading-[140%] sm:mb-8
/* 小屏笔记本字体 */
md:text-[1.5rem] md:leading-[140%] md:mb-10
/* 大屏笔记本字体 */
lg:text-[1.75rem] lg:leading-[140%] lg:mb-12
/* 桌面端字体 */
xl:text-[2rem] xl:leading-[140%] xl:mb-14
/* 大屏显示器字体 */
2xl:text-[2.25rem] 2xl:leading-[140%] 2xl:mb-16"
>
Everyone is a movie master.
</p>
<div
className="flex justify-center items-center font-normal border border-white rounded-full bg-white/30 cursor-pointer
/* 移动端按钮 */
w-[8rem] h-[2.5rem] text-sm
/* 平板按钮 */
sm:w-[9rem] sm:h-[2.75rem] sm:text-base
/* 小屏笔记本按钮 */
md:w-[10rem] md:h-[3rem] md:text-base
/* 大屏笔记本按钮 */
lg:w-[11rem] lg:h-[3.25rem] lg:text-lg
/* 桌面端按钮 */
xl:w-[11.5rem] xl:h-[3.5rem] xl:text-lg
/* 大屏显示器按钮 */
2xl:w-[12rem] 2xl:h-[3.75rem] 2xl:text-xl"
onClick={() => {
if (localStorage.getItem("token")) {
router.push("/movies");
} else {
router.push("/login");
}
}}
>
Make a Movie
</div>
</div>
</div>
);
}
/**核心价值 */
function HomeModule2() {
const [isMobile, setIsMobile] = useState(true);
// 检测屏幕尺寸并设置状态
useEffect(() => {
const checkScreenSize = () => {
setIsMobile(window.innerWidth < 640);
};
// 初始检查
checkScreenSize();
// 监听窗口大小变化
}, []);
const videoList = [
{
title: "Text to Movie",
video: "https://cdn.qikongjian.com/1756559467841_kn9fr9.mp4",
},
{
title: "Image to Movie",
video: "https://cdn.qikongjian.com/1756559467840_m4ijd7.mp4",
},
{
title: "Template to Movie",
video: "https://cdn.qikongjian.com/1756559467836_ij9y54.mp4",
},
];
return (
<div
data-alt="core-value-section"
className="home-module2 relative flex flex-col items-center justify-center w-full bg-black snap-start
/* 移动端适配 */
h-[80vh] py-8
/* 平板适配 */
sm:h-[100vh] sm:py-12
/* 小屏笔记本适配 (13-15寸) */
md:h-[1000px] md:py-16
/* 大屏笔记本适配 (16-17寸) */
lg:h-[1000px] lg:py-20
/* 桌面端适配 (21-24寸) */
xl:h-[1000px] xl:py-24
/* 大屏显示器适配 (27寸+) */
2xl:h-[1000px] 2xl:py-32"
>
<div
data-alt="core-value-content"
className="center z-10 flex flex-col items-center px-4
/* 移动端间距 */
mb-8
/* 平板间距 */
sm:mb-8
/* 小屏笔记本间距 */
md:mb-10
/* 大屏笔记本间距 */
lg:mb-12
/* 桌面端间距 */
xl:mb-16
/* 大屏显示器间距 */
2xl:mb-20"
>
<h2
className="text-white font-normal text-center
/* 移动端字体 */
text-[2rem] leading-[110%] mb-4
/* 平板字体 */
sm:text-[2.5rem] sm:leading-[110%] sm:mb-6
/* 小屏笔记本字体 */
md:text-[3rem] md:leading-[110%] md:mb-8
/* 大屏笔记本字体 */
lg:text-[3.25rem] lg:leading-[110%] lg:mb-10
/* 桌面端字体 */
xl:text-[3.375rem] xl:leading-[110%] xl:mb-12
/* 大屏显示器字体 */
2xl:text-[3.5rem] 2xl:leading-[110%] 2xl:mb-16"
>
Just Drop A Thought
</h2>
<p
className="text-white font-normal text-center
/* 移动端字体 */
text-[1rem] leading-[140%]
/* 平板字体 */
sm:text-[1.25rem] sm:leading-[140%]
/* 小屏笔记本字体 */
md:text-[1.5rem] md:leading-[140%]
/* 大屏笔记本字体 */
lg:text-[1.6rem] lg:leading-[140%]
/* 桌面端字体 */
xl:text-[1.7rem] xl:leading-[140%]
/* 大屏显示器字体 */
2xl:text-[1.8rem] 2xl:leading-[140%]"
>
Say your idea in a single line,and MovieFlow will bring it to life.
</p>
</div>
<div
data-alt="core-value-videos"
className="w-full px-4
/* 移动端容器 */
h-[20rem]
/* 平板容器 */
sm:h-[24rem] sm:px-6
/* 小屏笔记本容器 */
md:h-[28rem] md:px-8
/* 大屏笔记本容器 */
lg:h-[32rem] lg:px-12
/* 桌面端容器 */
xl:h-[36rem] xl:px-16
/* 大屏显示器容器 */
2xl:h-[40rem] 2xl:px-20"
>
<Swiper
key={isMobile ? "mobile-swiper" : "desktop-swiper"}
effect={isMobile ? "cube" : "coverflow"}
grabCursor={true}
initialSlide={1}
cubeEffect={
isMobile
? {
shadow: true,
slideShadows: true,
shadowOffset: 20,
shadowScale: 0.94,
}
: undefined
}
coverflowEffect={
!isMobile
? {
rotate: 50,
stretch: 0,
depth: 100,
modifier: 1,
slideShadows: true,
}
: undefined
}
centeredSlides={!isMobile}
slidesPerView={isMobile ? 1 : "auto"}
pagination={
isMobile
? true
: {
clickable: true,
dynamicBullets: true,
}
}
modules={
isMobile ? [EffectCube, Pagination] : [EffectCoverflow, Pagination]
}
className="w-full h-full
/* 分页器样式 - 白色线条 */
[&_.swiper-pagination-bullet]:!bg-white [&_.swiper-pagination-bullet]:!w-5 [&_.swiper-pagination-bullet]:!h-0.5 [&_.swiper-pagination-bullet]:!rounded-none [&_.swiper-pagination-bullet]:!mx-1
/* Cube 效果样式 */
[&_.swiper-wrapper]:!transform-style-preserve-3d [&_.swiper-slide]:!transform-style-preserve-3d"
onSlideChange={(swiper) => {
// 只播放当前活跃的视频
const activeSlide = swiper.slides[swiper.activeIndex];
const activeVideo = activeSlide?.querySelector("video");
if (activeVideo) {
activeVideo.play().catch(() => {
// 忽略播放错误
});
}
}}
onSwiper={(swiper) => {
// 初始化时播放第二个视频索引为1
const secondVideo = swiper.slides[1]?.querySelector("video");
if (secondVideo) {
secondVideo.play().catch(() => {
// 忽略播放错误
});
}
}}
>
{videoList.map((item, index) => (
<SwiperSlide
key={index}
className={`flex flex-col items-center justify-center ${
isMobile
? "w-full h-full"
: "w-[320px] h-full md:w-[360px] lg:w-[400px] xl:w-[440px] 2xl:w-[480px]"
}`}
>
<video
src={item.video}
poster={`${item.video}?vframe/jpg/offset/1`}
loop
muted
playsInline
preload="none"
className={`object-cover border border-white/20 rounded-2xl w-full ${
isMobile
? "h-[12rem]"
: "h-[14rem] md:h-[16rem] lg:h-[18rem] xl:h-[20rem] 2xl:h-[22rem]"
}`}
/>
<h3
className={`mt-4 text-white font-medium text-center ${
isMobile
? "text-[1rem]"
: "text-[1.125rem] md:text-[1.25rem] lg:text-[1.375rem] xl:text-[1.5rem] 2xl:text-[1.625rem]"
}`}
>
{item.title}
</h3>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
);
}
/**案例展示 */
function HomeModule3() {
const [isMobile, setIsMobile] = useState(true);
// 检测屏幕尺寸并设置状态
useEffect(() => {
const checkScreenSize = () => {
setIsMobile(window.innerWidth < 640);
};
// 初始检查
checkScreenSize();
// // 监听窗口大小变化
}, []);
// PC端二维数组数据
const pcVideoList = [
[
"https://cdn.qikongjian.com/1756474023656_60twk5.mp4",
"https://cdn.qikongjian.com/1756474023644_14n7is.mp4",
"https://cdn.qikongjian.com/1756474023648_kocq6z.mp4",
"https://cdn.qikongjian.com/1756474023657_w10boo.mp4",
"https://cdn.qikongjian.com/1756474023657_nf8799.mp4",
"https://cdn.qikongjian.com/1756474230992_vw0ubf.mp4",
],
[
"https://cdn.qikongjian.com/1756474023655_pov4c3.mp4",
"https://cdn.qikongjian.com/1756474023663_yohi7a.mp4",
"https://cdn.qikongjian.com/1756474023661_348dx3.mp4",
"https://cdn.qikongjian.com/1756474023683_xlb34s.mp4",
"https://cdn.qikongjian.com/1756474230987_63ooji.mp4",
],
[
"https://cdn.qikongjian.com/1756474230997_zysje8.mp4",
"https://cdn.qikongjian.com/1756474230988_tgqzln.mp4",
"https://cdn.qikongjian.com/1756474231007_qneeia.mp4",
"https://cdn.qikongjian.com/1756474231008_qyqtka.mp4",
"https://cdn.qikongjian.com/1756474231009_vs49d9.mp4",
"https://cdn.qikongjian.com/1756474231010_2a48p0.mp4",
],
];
// 移动端一维数组数据(合并所有视频)
const mobileVideoList = [
"https://cdn.qikongjian.com/1756474023656_60twk5.mp4",
"https://cdn.qikongjian.com/1756474023644_14n7is.mp4",
"https://cdn.qikongjian.com/1756474023648_kocq6z.mp4",
"https://cdn.qikongjian.com/1756474023657_w10boo.mp4",
"https://cdn.qikongjian.com/1756474023657_nf8799.mp4",
"https://cdn.qikongjian.com/1756474230992_vw0ubf.mp4",
"https://cdn.qikongjian.com/1756474023655_pov4c3.mp4",
"https://cdn.qikongjian.com/1756474023663_yohi7a.mp4",
"https://cdn.qikongjian.com/1756474023661_348dx3.mp4",
"https://cdn.qikongjian.com/1756474023683_xlb34s.mp4",
"https://cdn.qikongjian.com/1756474230987_63ooji.mp4",
"https://cdn.qikongjian.com/1756474230997_zysje8.mp4",
"https://cdn.qikongjian.com/1756474230988_tgqzln.mp4",
"https://cdn.qikongjian.com/1756474231007_qneeia.mp4",
"https://cdn.qikongjian.com/1756474231008_qyqtka.mp4",
"https://cdn.qikongjian.com/1756474231009_vs49d9.mp4",
"https://cdn.qikongjian.com/1756474231010_2a48p0.mp4",
];
return (
<div
className="home-module3 relative flex flex-col items-center justify-center w-full bg-black snap-start
/* 移动端适配 */
h-[80vh] py-8
/* 平板适配 */
sm:h-[100vh] sm:py-12
/* 小屏笔记本适配 (13-15寸) */
md:h-[1300px] md:py-16
/* 大屏笔记本适配 (16-17寸) */
lg:h-[1300px] lg:py-20
/* 桌面端适配 (21-24寸) */
xl:h-[1300px] xl:py-24
/* 大屏显示器适配 (27寸+) */
2xl:h-[1300px] 2xl:py-32"
>
<div
className="center z-10 flex flex-col items-center px-4
/* 移动端间距 */
-mb-8
/* 平板间距 */
sm:mb-12
/* 小屏笔记本间距 */
md:mb-16
/* 大屏笔记本间距 */
lg:mb-20
/* 桌面端间距 */
xl:mb-24
/* 大屏显示器间距 */
2xl:mb-32"
>
<h2
className="text-white font-normal text-center
/* 移动端字体 */
text-[2rem] leading-[110%] mb-4
/* 平板字体 */
sm:text-[2.5rem] sm:leading-[110%] sm:mb-6
/* 小屏笔记本字体 */
md:text-[3rem] md:leading-[110%] md:mb-8
/* 大屏笔记本字体 */
lg:text-[3.25rem] lg:leading-[110%] lg:mb-10
/* 桌面端字体 */
xl:text-[3.375rem] xl:leading-[110%] xl:mb-12
/* 大屏显示器字体 */
2xl:text-[3.5rem] 2xl:leading-[110%] 2xl:mb-16"
>
Ideas Made Real
</h2>
<p
className="text-white font-normal text-center
/* 移动端字体 */
text-[1rem] leading-[140%]
/* 平板字体 */
sm:text-[1.25rem] sm:leading-[140%]
/* 小屏笔记本字体 */
md:text-[1.5rem] md:leading-[140%]
/* 大屏笔记本字体 */
lg:text-[1.6rem] lg:leading-[140%]
/* 桌面端字体 */
xl:text-[1.7rem] xl:leading-[140%]
/* 大屏显示器字体 */
2xl:text-[1.8rem] 2xl:leading-[140%]"
>
High-quality films, any style, made with MovieFlow.
</p>
</div>
{/* 移动端 - 单列布局 */}
{isMobile ? (
<div
data-alt="mobile-single-column"
className="w-full px-4 h-[40rem] relative"
>
{/* 上方阴影遮罩 */}
<div
className="absolute -top-[1rem] -left-0 w-full h-[8rem] 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>
{/* 下方阴影遮罩 */}
<div
className="absolute -bottom-[1rem] -left-0 w-full h-[8rem] 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>
<Swiper
modules={[Autoplay]}
direction="vertical"
loop={true}
spaceBetween={0}
slidesPerView={3}
autoplay={{
delay: 0,
pauseOnMouseEnter: true,
disableOnInteraction: false,
}}
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
}
>
{mobileVideoList.map((video, videoIndex) => (
<SwiperSlide key={videoIndex} className="w-full !h-[12rem]">
<div className="w-full h-[11rem] bg-gray-800 rounded-2xl overflow-hidden">
<video
src={video}
poster={`${video}?vframe/jpg/offset/1`}
autoPlay
loop
muted
playsInline
preload="none"
className="w-full h-full object-cover"
onMouseEnter={(e) => {
const videoElement = e.currentTarget;
videoElement.play();
}}
/>
</div>
</SwiperSlide>
))}
</Swiper>
</div>
) : (
/* PC端 - 三列网格布局 */
<div
data-alt="pc-grid-layout"
className="grid grid-cols-3 gap-4 w-full h-[64rem] px-8 relative
/* 平板及以上 - 有最大宽度限制 */
sm:max-w-[1200px] sm:mx-auto sm:gap-6
/* 小屏笔记本 */
md:max-w-[1400px] md:gap-8
/* 大屏笔记本 */
lg:max-w-[1600px] lg:gap-8
/* 桌面端 */
xl:max-w-[1800px] xl:gap-8
/* 大屏显示器 */
2xl:max-w-[2000px] 2xl:gap-8 "
>
{/* 上方阴影遮罩 */}
<div
className="absolute -top-[1rem] -left-0 w-full h-[20rem] z-10 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>
{/* 下方阴影遮罩 */}
<div
className="absolute -bottom-[1rem] -left-0 w-full h-[20rem] z-10 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>
{pcVideoList.map((column, columnIndex) => (
<div key={columnIndex} className="w-full h-[64rem] relative">
<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} className="w-full !h-[20rem]">
<div className="w-full h-[19rem] bg-gray-800 rounded-2xl overflow-hidden aspect-[1/0.77]">
<video
src={video}
poster={`${video}?vframe/jpg/offset/1`}
loop
muted
playsInline
preload="none"
className="w-full h-full object-cover aspect-[1/0.77]"
onMouseEnter={(e) => {
const videoElement = e.currentTarget;
videoElement.play();
}}
/>
</div>
</SwiperSlide>
))}
</Swiper>
</div>
))}
</div>
)}
</div>
);
}
/**电影制作工序介绍 */
function HomeModule4() {
const [activeTab, setActiveTab] = useState(0);
const [isMobile, setIsMobile] = useState(true);
// 检测屏幕尺寸并设置状态
useEffect(() => {
const checkScreenSize = () => {
setIsMobile(window.innerWidth < 768);
};
// 初始检查
checkScreenSize();
}, []);
const processSteps = [
{
title: " The Story Agent",
description:
" From a single thought, it builds entire worlds and compelling plots.",
video: "https://cdn.qikongjian.com/videos/module4 (3).mp4",
},
{
title: " AI Character Agent",
description:
"Cast your virtual actors. Lock them in once, for the entire story.",
video: "https://cdn.qikongjian.com/videos/module4 (1).mp4",
},
{
title: " The Shot Agent",
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 Clip Agent",
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 relative flex flex-col items-center justify-center w-full bg-black snap-start
/* 移动端适配 */
h-[100vh] py-8 px-2
/* 平板适配 */
sm:h-[1000px] sm:py-12 sm:px-6
/* PC端全部使用视口高度更好地适应不同屏幕 */
md:min-h-[1000px] md:py-16 md:px-6
lg:min-h-[1100px] lg:py-20 lg:px-6
xl:min-h-[1200px] xl:py-24 xl:px-6
2xl:min-h-[1300px] 2xl:py-32 2xl:px-6"
>
<div
data-alt="core-value-content"
className="center z-10 flex flex-col items-center
/* 移动端间距和高度 */
px-4 h-[14rem]
/* 平板间距和高度 */
sm:px-6 sm:h-[16rem]
/* 小屏笔记本间距和高度 */
md:px-8 md:h-[8rem]
/* 大屏笔记本间距和高度 */
lg:px-12 lg:h-[12rem]
/* 桌面端间距和高度 */
xl:px-16 xl:h-[14rem]
/* 大屏显示器间距和高度 */
2xl:px-20 2xl:h-[16rem]"
>
<h2
className="text-white font-normal text-center
/* 移动端字体 */
text-[1.5rem] leading-[110%]
/* 平板字体 */
sm:text-[2rem] sm:leading-[110%]
/* 小屏笔记本字体 */
md:text-[2.5rem] md:leading-[110%]
/* 大屏笔记本字体 */
lg:text-[3rem] lg:leading-[110%]
/* 桌面端字体 */
xl:text-[3.375rem] xl:leading-[110%]
/* 大屏显示器字体 */
2xl:text-[3.5rem] 2xl:leading-[110%]"
>
Create Your Way
</h2>
</div>
{isMobile ? (
/* 移动端 - 垂直布局 */
<div className="w-full px-4 space-y-6">
{/* 视频播放区域 */}
<div className="w-full">
<div className="w-full h-[12rem] bg-gray-800 rounded-2xl overflow-hidden border border-white/20">
<video
key={activeTab}
src={processSteps[activeTab].video}
poster={`${processSteps[activeTab].video}?vframe/jpg/offset/1`}
autoPlay
loop
muted
playsInline
preload="none"
className="w-full h-full object-cover"
/>
</div>
</div>
{/* 切换tabs */}
<div className="grid grid-cols-2 gap-3">
{processSteps.map((step, index) => (
<div
key={index}
onClick={() => handleTabClick(index)}
className={`p-3 rounded-xl cursor-pointer transition-all duration-300 border ${
activeTab === index
? "bg-[#262626] border-white/20"
: "bg-black border-white/10"
}`}
>
<h3
className={`text-sm font-normal mb-2 ${
activeTab === index ? "text-white" : "text-white/70"
}`}
>
{step.title}
</h3>
<p
className={`text-xs leading-[140%] ${
activeTab === index ? "text-white/80" : "text-white/50"
}`}
>
{step.description}
</p>
</div>
))}
</div>
</div>
) : (
/* PC端 - 水平布局 */
<div
className="flex w-full gap-[1rem]
/* 平板及以上 - 间距和内边距 */
sm:px-4 sm:gap-2
/* 小屏笔记本 */
md:px-[3rem] md:gap-[1.5rem]
/* 大屏笔记本 */
lg:px-[4rem] lg:gap-[2rem]
/* 桌面端 */
xl:px-[5rem] xl:gap-[2.5rem]
/* 大屏显示器 */
2xl:px-[6rem] 2xl:gap-[3rem]
/* 设置具体高度覆盖flex-1的自动填充 */
sm:h-[60%]
md:h-[65%]
lg:h-[70%]
xl:h-[75%]
2xl:h-[65%]
/* 超宽屏幕适配 */
min-[1920px]:h-[70%]
min-[2560px]:h-[80%]"
>
{/* 左侧四个切换tab */}
<div className="flex flex-col gap-[1rem] h-full justify-center">
{processSteps.map((step, index) => (
<div
key={index}
onClick={() => handleTabClick(index)}
className={`rounded-2xl cursor-pointer transition-all flex-1 duration-300 border ${
activeTab === index
? "bg-[#262626] border-white/20 hover:border-white/40"
: "bg-black border-white/10 hover:border-white/40"
}
/* 平板及以上 - 使用固定高度,让外层盒子撑起来 */
sm:w-[12rem]
/* 小屏笔记本 - 13-15寸适配 */
md:w-[14rem]
/* 大屏笔记本 - 16-17寸适配 */
lg:w-[16rem]
/* 桌面端 - 21-24寸 */
xl:w-[22rem]
/* 大屏显示器 - 27寸+ */
2xl:w-[32rem]`}
>
<div
className="h-full flex flex-col justify-center
/* 平板及以上 - 内边距适配 */
sm:p-[0.75rem]
/* 小屏笔记本 - 13-15寸 */
md:p-[0.875rem]
/* 大屏笔记本 - 16-17寸 */
lg:p-[1rem]
/* 桌面端 - 21-24寸 */
xl:p-[1.25rem]
/* 大屏显示器 - 27寸+ */
2xl:p-[1.5rem]"
>
<h3
className={`font-normal ${
activeTab === index ? "text-white" : "text-white/70"
}
/* 平板及以上 - 字体适配 */
sm:text-[1rem] sm:mb-[0.5rem]
/* 小屏笔记本 - 13-15寸 */
md:text-[1.125rem] md:mb-[0.625rem]
/* 大屏笔记本 - 16-17寸 */
lg:text-[1.25rem] lg:mb-[0.75rem]
/* 桌面端 - 21-24寸 */
xl:text-[1.5rem] xl:mb-[1rem]
/* 大屏显示器 - 27寸+ */
2xl:text-[1.75rem] 2xl:mb-[1.25rem]`}
>
{step.title}
</h3>
<p
className={`leading-[140%] ${
activeTab === index ? "text-white" : "text-white/70"
}
/* 平板及以上 - 字体适配 */
sm:text-[0.7rem]
/* 小屏笔记本 - 13-15寸 */
md:text-[0.75rem]
/* 大屏笔记本 - 16-17寸 */
lg:text-[0.75rem]
/* 桌面端 - 21-24寸 */
xl:text-[1rem]
/* 大屏显示器 - 27寸+ */
2xl:text-[1rem]`}
>
{step.description}
</p>
</div>
</div>
))}
</div>
{/* 右侧视频播放区域 */}
<div className="flex-1 flex justify-center items-center">
<div
className="bg-gray-800 rounded-2xl overflow-hidden border border-white/20
/* 平板及以上 - 使用100%高度,让外层盒子撑起来 */
sm:w-[85%] sm:h-full
/* 小屏笔记本 - 13-15寸适配 */
md:w-[100%] md:h-full
/* 大屏笔记本 - 16-17寸适配 */
lg:w-[100%] lg:h-full
/* 桌面端 - 21-24寸 */
xl:w-[100%] xl:h-full
/* 大屏显示器 - 27寸+ */
2xl:w-[100%] 2xl:h-full"
>
<video
key={activeTab}
src={processSteps[activeTab].video}
poster={`${processSteps[activeTab].video}?vframe/jpg/offset/1`}
autoPlay
loop
muted
playsInline
preload="none"
className="w-full h-full object-cover"
/>
</div>
</div>
</div>
)}
</div>
);
}
/**价格方案 */
function HomeModule5() {
const [billingType, setBillingType] = useState<"month" | "year">("year");
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;
originalPrice: number;
monthlyPrice: number;
discountMsg: string;
credits: string;
buttonText: string;
features: string[];
issubscribed: boolean;
}[]
>(() => {
return plans.map((plan) => {
return {
title: plan.display_name || plan.name,
price:
billingType === "month"
? plan.price_month / 100
: plan.price_year / 100,
originalPrice: plan.price_month / 100,
monthlyPrice: billingType === "month" ? 0 : Math.round(plan.price_year / 12) / 100,
discountMsg: `Saves $${(plan.price_month * 12 - plan.price_year) / 100} by billing yearly!`,
credits: plan.description,
buttonText: plan.is_free ? "Try For Free" : "Subscribe Now",
issubscribed: plan.is_subscribed,
features: plan.features || [],
};
});
}, [plans, billingType]);
const handleSubscribe = async (planName: string) => {
localStorage.setItem("callBackUrl", pathname);
// 改为直接携带参数打开 pay-redirect由其内部完成创建与跳转
const url = `/pay-redirect?type=subscription&plan=${encodeURIComponent(planName)}&billing=${encodeURIComponent(billingType)}`;
const win = window.open(url, "_blank");
// 通知当前窗口等待支付显示loading模态框
window.postMessage({
type: 'waiting-payment',
paymentType: 'subscription',
}, '*');
if (!win) {
throw new Error("Unable to open redirect window, please check popup settings");
}
};
return (
<div
data-alt="core-value-section"
className="home-module5 relative flex flex-col items-center justify-center w-full bg-black snap-start
/* 移动端适配 */
min-h-[100vh] py-8
/* 平板适配 */
sm:min-h-[100vh] sm:py-12
/* 小屏笔记本适配 (13-15寸) */
md:h-[1000px] md:py-16
/* 大屏笔记本适配 (16-17寸) */
lg:h-[1100px] lg:py-20
/* 桌面端适配 (21-24寸) */
xl:h-[1200px] xl:py-24
/* 大屏显示器适配 (27寸+) */
2xl:h-[1500px] 2xl:py-32"
>
<div
data-alt="core-value-content"
className="center z-10 flex flex-col items-center
/* 移动端间距 */
mb-8 px-4
/* 平板间距 */
sm:mb-12 sm:px-6
/* 小屏笔记本间距 */
md:mb-[4rem] md:px-8
/* 大屏笔记本间距 */
lg:mb-[4rem] lg:px-12
/* 桌面端间距 */
xl:mb-[4rem] xl:px-16
/* 大屏显示器间距 */
2xl:mb-[4rem] 2xl:px-20"
>
<h2
className="text-white font-normal text-center
/* 移动端字体 */
text-[1.5rem] leading-[110%] mb-4
/* 平板字体 */
sm:text-[2rem] sm:leading-[110%] sm:mb-6
/* 小屏笔记本字体 */
md:text-[2.5rem] md:leading-[110%] md:mb-[1.5rem]
/* 大屏笔记本字体 */
lg:text-[3rem] lg:leading-[110%] lg:mb-[1.5rem]
/* 桌面端字体 */
xl:text-[3.375rem] xl:leading-[110%] xl:mb-[1.5rem]
/* 大屏显示器字体 */
2xl:text-[3.5rem] 2xl:leading-[110%] 2xl:mb-[1.5rem]"
>
Pick a plan and make it yours
</h2>
{/* 计费切换 */}
<div
className="flex bg-black rounded-full border border-white/20
/* 移动端尺寸 */
h-[2.5rem] p-[0.0625rem] mt-4
/* 平板尺寸 */
sm:h-[3rem] sm:mt-6
/* 小屏笔记本尺寸 */
md:h-[3.375rem] md:mt-[1.5rem]
/* 大屏笔记本尺寸 */
lg:h-[3.375rem] lg:mt-[1.5rem]
/* 桌面端尺寸 */
xl:h-[3.375rem] xl:mt-[1.5rem]
/* 大屏显示器尺寸 */
2xl:h-[3.375rem] 2xl:mt-[1.5rem]"
>
<button
onClick={() => setBillingType("month")}
className={`box-border flex justify-center items-center rounded-full transition-all duration-300 ${
billingType === "month"
? "bg-white text-black"
: "text-white hover:text-gray-300"
}
/* 移动端按钮尺寸 */
w-[4.5rem] text-sm
/* 平板按钮尺寸 */
sm:w-[5rem] sm:text-sm
/* 小屏笔记本按钮尺寸 */
md:w-[6rem] md:text-base
/* 大屏笔记本按钮尺寸 */
lg:w-[6rem] lg:text-base
/* 桌面端按钮尺寸 */
xl:w-[6rem] xl:text-base
/* 大屏显示器按钮尺寸 */
2xl:w-[6rem] 2xl:text-base`}
>
Monthly
</button>
<button
onClick={() => setBillingType("year")}
className={`box-border flex justify-center items-center rounded-full transition-all duration-300 ${
billingType === "year"
? "bg-white text-black"
: "text-white hover:text-gray-300"
}
/* 移动端按钮尺寸 */
w-[5.5rem] text-sm
/* 平板按钮尺寸 */
sm:w-[6rem] sm:text-sm
/* 小屏笔记本按钮尺寸 */
md:w-[7.125rem] md:text-base
/* 大屏笔记本按钮尺寸 */
lg:w-[7.125rem] lg:text-base
/* 桌面端按钮尺寸 */
xl:w-[7.125rem] xl:text-base
/* 大屏显示器按钮尺寸 */
2xl:w-[7.125rem] 2xl:text-base`}
>
Yearly
</button>
</div>
</div>
{/* 主要价格卡片 */}
<div
className="w-full max-w-[88%] mx-auto px-4
/* 移动端 - 单列布局 */
grid grid-cols-1 gap-2
/* 平板 - 双列布局 */
sm:grid-cols-2 sm:gap-6 sm:px-6
/* 桌面端 - 三列布局 */
md:grid-cols-3 md:gap-8 md:px-8
/* 大屏 - 保持三列但增加间距 */
lg:gap-10 lg:px-12
xl:gap-12 xl:px-16
2xl:gap-16 2xl:px-20"
>
{pricingPlans.map((plan, index) => (
<div
key={index}
className="bg-black rounded-2xl border border-white/20
/* 移动端卡片尺寸 */
p-4 min-h-[28rem]
/* 平板卡片尺寸 */
sm:p-5 sm:min-h-[32rem]
/* 小屏笔记本卡片尺寸 */
md:p-6 md:min-h-[36rem]
/* 大屏笔记本卡片尺寸 */
lg:p-[1.375rem] lg:min-h-[37rem]
/* 桌面端卡片尺寸 */
xl:p-[1.5rem] xl:min-h-[38.125rem]
/* 大屏显示器卡片尺寸 */
2xl:p-[1.75rem] 2xl:min-h-[40rem]"
>
<h3
className="text-white font-normal
/* 移动端标题 */
text-lg mb-3
/* 平板标题 */
sm:text-xl sm:mb-4
/* 小屏笔记本标题 */
md:text-xl md:mb-4
/* 大屏笔记本标题 */
lg:text-2xl lg:mb-[1rem]
/* 桌面端标题 */
xl:text-2xl xl:mb-[1rem]
/* 大屏显示器标题 */
2xl:text-3xl 2xl:mb-[1.25rem]"
>
{plan.title}
</h3>
<div
className="mb-3
/* 平板间距 */
sm:mb-4
/* 小屏笔记本间距 */
md:mb-4
/* 大屏笔记本间距 */
lg:mb-[1rem]
/* 桌面端间距 */
xl:mb-[1rem]
/* 大屏显示器间距 */
2xl:mb-[1.25rem]"
>
<div className="flex items-baseline">
<span
className="text-white font-bold
/* 移动端价格字体 */
text-2xl
/* 平板价格字体 */
sm:text-3xl
/* 小屏笔记本价格字体 */
md:text-[2.5rem]
/* 大屏笔记本价格字体 */
lg:text-[3rem]
/* 桌面端价格字体 */
xl:text-[3.375rem]
/* 大屏显示器价格字体 */
2xl:text-[3.75rem]"
>
${plan.monthlyPrice || plan.price}
</span>
<span
className="text-white ml-2 whitespace-nowrap
/* 移动端单位字体 */
text-xs
/* 平板单位字体 */
sm:text-xs
/* 小屏笔记本单位字体 */
md:text-xs
/* 大屏笔记本单位字体 */
lg:text-xs
/* 桌面端单位字体 */
xl:text-xs
/* 大屏显示器单位字体 */
2xl:text-sm"
>
/ month
</span>
</div>
{plan.originalPrice !== plan.price ? (
<div className="pt-2 text-white text-sm line-through">
${plan.originalPrice}
</div>
) : null}
</div>
<p
className="text-white mb-4
/* 移动端描述字体 */
text-sm
/* 平板描述字体 */
sm:text-sm
/* 小屏笔记本描述字体 */
md:text-[0.875rem]
/* 大屏笔记本描述字体 */
lg:text-[0.875rem]
/* 桌面端描述字体 */
xl:text-[0.875rem]
/* 大屏显示器描述字体 */
2xl:text-base"
>
{plan.credits}
</p>
{plan.issubscribed ? (
<button
disabled
className="w-full bg-gray-400 text-gray-600 rounded-full cursor-not-allowed border border-gray-300
/* 移动端按钮 */
py-2 mb-4 text-sm
/* 平板按钮 */
sm:py-3 sm:mb-4 sm:text-base
/* 小屏笔记本按钮 */
md:py-[0.75rem] md:mb-[1rem] md:text-base
/* 大屏笔记本按钮 */
lg:py-[0.75rem] lg:mb-[1rem] lg:text-base
/* 桌面端按钮 */
xl:py-[0.75rem] xl:mb-[1rem] xl:text-base
/* 大屏显示器按钮 */
2xl:py-[0.875rem] 2xl:mb-[1.25rem] 2xl:text-lg"
>
Already Owned
</button>
) : (
<button
onClick={() => handleSubscribe(plan.title)}
className="w-full bg-white text-black rounded-full hover:bg-black hover:text-white transition-colors border border-white/20
/* 移动端按钮 */
py-2 mb-4 text-sm
/* 平板按钮 */
sm:py-3 sm:mb-4 sm:text-base
/* 小屏笔记本按钮 */
md:py-[0.75rem] md:mb-[1rem] md:text-base
/* 大屏笔记本按钮 */
lg:py-[0.75rem] lg:mb-[1rem] lg:text-base
/* 桌面端按钮 */
xl:py-[0.75rem] xl:mb-[1rem] xl:text-base
/* 大屏显示器按钮 */
2xl:py-[0.875rem] 2xl:mb-[1.25rem] 2xl:text-lg"
>
{plan.buttonText}
</button>
)}
<p
className="w-full text-center text-white/60 mb-4
/* 移动端提示文字 */
text-xs
/* 平板提示文字 */
sm:text-xs
/* 小屏笔记本提示文字 */
md:text-[0.75rem] md:mb-[2rem]
/* 大屏笔记本提示文字 */
lg:text-[0.75rem] lg:mb-[2rem]
/* 桌面端提示文字 */
xl:text-[0.75rem] xl:mb-[2rem]
/* 大屏显示器提示文字 */
2xl:text-sm 2xl:mb-[2.5rem]"
>
* {plan.discountMsg}
</p>
<ul
className="space-y-2
/* 平板特性列表间距 */
sm:space-y-3
/* 小屏笔记本特性列表间距 */
md:space-y-[1rem]
/* 大屏笔记本特性列表间距 */
lg:space-y-[1rem]
/* 桌面端特性列表间距 */
xl:space-y-[1rem]
/* 大屏显示器特性列表间距 */
2xl:space-y-[1.25rem]"
>
{plan.features.map((feature, featureIndex) => (
<li
key={featureIndex}
className="flex items-center text-white
/* 移动端特性文字 */
text-sm
/* 平板特性文字 */
sm:text-sm
/* 小屏笔记本特性文字 */
md:text-[0.875rem]
/* 大屏笔记本特性文字 */
lg:text-[0.875rem]
/* 桌面端特性文字 */
xl:text-[0.875rem]
/* 大屏显示器特性文字 */
2xl:text-base"
>
<span
className="text-[#C73BFF] mr-2
/* 移动端勾号间距 */
text-sm
/* 平板勾号间距 */
sm:mr-2 sm:text-base
/* 小屏笔记本勾号间距 */
md:mr-[0.5rem] md:text-base
/* 大屏笔记本勾号间距 */
lg:mr-[0.5rem] lg:text-base
/* 桌面端勾号间距 */
xl:mr-[0.5rem] xl:text-base
/* 大屏显示器勾号间距 */
2xl:mr-[0.625rem] 2xl:text-lg"
>
</span>
{feature}
</li>
))}
</ul>
</div>
))}
</div>
</div>
);
}