feat(pages): 添加联系、首页和作品页面,更新样式和配置文件

This commit is contained in:
2026-06-26 10:36:54 +08:00
commit 44f0b43fcc
31 changed files with 5557 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
import { useState, useEffect, useRef } from 'react'
interface AnimatedCounterProps {
end: number
suffix?: string
duration?: number
}
export default function AnimatedCounter({ end, suffix = '', duration = 2000 }: AnimatedCounterProps) {
const [count, setCount] = useState(0)
const [isVisible, setIsVisible] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true)
observer.disconnect()
}
},
{ threshold: 0.3 }
)
if (ref.current) {
observer.observe(ref.current)
}
return () => observer.disconnect()
}, [])
useEffect(() => {
if (!isVisible) return
let startTime: number
let animationId: number
const animate = (currentTime: number) => {
if (!startTime) startTime = currentTime
const elapsed = currentTime - startTime
const progress = Math.min(elapsed / duration, 1)
const easeOut = 1 - Math.pow(1 - progress, 3)
setCount(Math.floor(easeOut * end))
if (progress < 1) {
animationId = requestAnimationFrame(animate)
}
}
animationId = requestAnimationFrame(animate)
return () => cancelAnimationFrame(animationId)
}, [isVisible, end, duration])
return (
<div ref={ref} className="text-4xl md:text-5xl font-bold gradient-text">
{count}{suffix}
</div>
)
}

View File

@@ -0,0 +1,166 @@
import { useState } from 'react'
import ScrollReveal from './ScrollReveal'
const comparisons = [
{
id: 1,
title: '品牌宣传片',
traditional: {
time: '2-4周',
cost: '¥50,000-200,000',
team: '导演+摄影+后期+演员',
process: '脚本撰写→选角→拍摄→后期制作→修改→交付'
},
ai: {
time: '1-3天',
cost: '¥5,000-20,000',
team: 'AI工程师+创意策划',
process: '需求确认→AI生成→人工优化→交付'
}
},
{
id: 2,
title: '产品展示视频',
traditional: {
time: '1-2周',
cost: '¥20,000-80,000',
team: '摄影师+灯光师+后期',
process: '产品准备→场景搭建→拍摄→精修→交付'
},
ai: {
time: '4-8小时',
cost: '¥2,000-8,000',
team: 'AI工程师',
process: '产品图片→AI建模→动画生成→交付'
}
},
{
id: 3,
title: '社交媒体短视频',
traditional: {
time: '3-7天',
cost: '¥10,000-30,000',
team: '编导+拍摄+剪辑',
process: '策划→拍摄→剪辑→字幕→交付'
},
ai: {
time: '2-4小时',
cost: '¥1,000-3,000',
team: 'AI工程师',
process: '文案输入→AI生成→一键发布'
}
}
]
export default function CaseComparison() {
const [activeCase, setActiveCase] = useState(comparisons[0])
return (
<section className="py-24 px-4 bg-[#0d0d2b]/50">
<div className="max-w-7xl mx-auto">
<ScrollReveal>
<div className="text-center mb-16">
<div className="inline-flex items-center px-4 py-2 rounded-full glass-card mb-6">
<span className="text-purple-400 text-sm font-medium"></span>
</div>
<h2 className="text-4xl md:text-5xl font-bold mb-4">
AI vs <span className="gradient-text"></span>
</h2>
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
AI制作效率提升10倍80%
</p>
</div>
</ScrollReveal>
{/* Case Tabs */}
<ScrollReveal delay={100}>
<div className="flex flex-wrap justify-center gap-3 mb-12">
{comparisons.map((comp) => (
<button
key={comp.id}
onClick={() => setActiveCase(comp)}
className={`px-5 py-2.5 rounded-xl font-medium transition-all duration-300 ${
activeCase.id === comp.id
? 'gradient-bg-animated text-white'
: 'glass-card text-gray-400 hover:text-white'
}`}
>
{comp.title}
</button>
))}
</div>
</ScrollReveal>
{/* Comparison Cards */}
<ScrollReveal delay={200}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Traditional */}
<div className="glass-card rounded-3xl p-8 border-red-500/20">
<div className="flex items-center gap-3 mb-8">
<div className="w-12 h-12 rounded-xl bg-red-500/20 flex items-center justify-center">
<svg className="w-6 h-6 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 className="text-xl font-bold text-white"></h3>
</div>
<div className="space-y-6">
<div className="flex items-center justify-between py-4 border-b border-white/5">
<span className="text-gray-400"></span>
<span className="text-red-400 font-semibold">{activeCase.traditional.time}</span>
</div>
<div className="flex items-center justify-between py-4 border-b border-white/5">
<span className="text-gray-400"></span>
<span className="text-red-400 font-semibold">{activeCase.traditional.cost}</span>
</div>
<div className="flex items-center justify-between py-4 border-b border-white/5">
<span className="text-gray-400"></span>
<span className="text-gray-300 text-sm text-right">{activeCase.traditional.team}</span>
</div>
<div>
<span className="text-gray-400 block mb-3"></span>
<p className="text-gray-300 text-sm leading-relaxed">{activeCase.traditional.process}</p>
</div>
</div>
</div>
{/* AI */}
<div className="glass-card rounded-3xl p-8 border-green-500/20 relative overflow-hidden">
<div className="absolute top-4 right-4 px-3 py-1 rounded-full text-xs font-semibold gradient-bg-animated">
</div>
<div className="flex items-center gap-3 mb-8">
<div className="w-12 h-12 rounded-xl bg-green-500/20 flex items-center justify-center">
<svg className="w-6 h-6 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<h3 className="text-xl font-bold text-white">AI制作</h3>
</div>
<div className="space-y-6">
<div className="flex items-center justify-between py-4 border-b border-white/5">
<span className="text-gray-400"></span>
<span className="text-green-400 font-semibold">{activeCase.ai.time}</span>
</div>
<div className="flex items-center justify-between py-4 border-b border-white/5">
<span className="text-gray-400"></span>
<span className="text-green-400 font-semibold">{activeCase.ai.cost}</span>
</div>
<div className="flex items-center justify-between py-4 border-b border-white/5">
<span className="text-gray-400"></span>
<span className="text-gray-300 text-sm">{activeCase.ai.team}</span>
</div>
<div>
<span className="text-gray-400 block mb-3"></span>
<p className="text-gray-300 text-sm leading-relaxed">{activeCase.ai.process}</p>
</div>
</div>
</div>
</div>
</ScrollReveal>
</div>
</section>
)
}

88
src/components/FAQ.tsx Normal file
View File

@@ -0,0 +1,88 @@
import { useState } from 'react'
import ScrollReveal from './ScrollReveal'
const faqs = [
{
question: 'AI生成的视频质量如何能达到商用标准吗',
answer: '我们的AI视频已经可以达到商用标准。通过多轮优化和人工精修最终输出的视频在画面质量、流畅度和创意表现上都能满足品牌宣传、社交媒体等商业场景的需求。我们服务过的100+客户中98%对最终效果表示满意。'
},
{
question: '制作一个AI视频需要多长时间',
answer: '根据视频复杂度不同制作周期从2小时到3天不等。简单的社交媒体短视频通常4-8小时即可完成而复杂的品牌宣传片可能需要1-3天。相比传统制作方式效率提升10倍以上。'
},
{
question: 'AI视频的版权归属如何',
answer: '所有通过我们生成的AI视频版权完全归属客户所有。我们使用的AI工具均拥有合法授权生成的内容不存在版权纠纷风险。同时我们会提供完整的版权证明文件。'
},
{
question: '你们使用哪些AI技术',
answer: '我们综合使用业界领先的AI工具包括Midjourney/Stable Diffusion图像生成、Runway/Pika Labs视频生成、HeyGen/D-ID数字人、ElevenLabs语音合成等。根据项目需求选择最合适的工具组合。'
},
{
question: '如何保证视频符合品牌调性?',
answer: '我们有专业的创意团队会在项目初期深入了解您的品牌调性、目标受众和传播目标。通过品牌指南学习、风格参考收集和多轮沟通确认确保AI生成的视频精准传达品牌价值。'
},
{
question: '支持哪些视频格式和尺寸?',
answer: '我们支持所有主流视频格式和尺寸包括横屏16:91920x1080/3840x2160、竖屏9:161080x1920、方形1:11080x1080等。同时支持MP4、MOV、AVI等格式输出满足不同平台的发布需求。'
}
]
export default function FAQ() {
const [openIndex, setOpenIndex] = useState<number | null>(0)
return (
<section className="py-24 px-4">
<div className="max-w-4xl mx-auto">
<ScrollReveal>
<div className="text-center mb-16">
<div className="inline-flex items-center px-4 py-2 rounded-full glass-card mb-6">
<span className="text-purple-400 text-sm font-medium"></span>
</div>
<h2 className="text-4xl md:text-5xl font-bold mb-4">
<span className="gradient-text">FAQ</span>
</h2>
<p className="text-gray-400 text-lg">
AI视频制作
</p>
</div>
</ScrollReveal>
<div className="space-y-4">
{faqs.map((faq, index) => (
<ScrollReveal key={index} delay={index * 50}>
<div className="glass-card rounded-2xl overflow-hidden">
<button
onClick={() => setOpenIndex(openIndex === index ? null : index)}
className="w-full text-left p-6 flex items-center justify-between gap-4"
>
<span className="text-white font-medium pr-4">{faq.question}</span>
<svg
className={`w-5 h-5 text-purple-400 flex-shrink-0 transition-transform duration-300 ${
openIndex === index ? 'rotate-180' : ''
}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
<div
className={`overflow-hidden transition-all duration-300 ${
openIndex === index ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'
}`}
>
<div className="px-6 pb-6">
<div className="h-px bg-white/10 mb-4" />
<p className="text-gray-400 leading-relaxed">{faq.answer}</p>
</div>
</div>
</div>
</ScrollReveal>
))}
</div>
</div>
</section>
)
}

46
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,46 @@
import { Link } from 'react-router-dom'
export default function Footer() {
return (
<footer className="bg-[#0d0d2b] border-t border-white/10">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<div className="flex items-center space-x-2 mb-4">
<div className="w-10 h-10 gradient-bg rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-xl">C</span>
</div>
<span className="text-xl font-bold gradient-text">CYH Technology</span>
</div>
<p className="text-gray-400 text-sm">
AI短视频制作公司
</p>
</div>
<div>
<h3 className="text-white font-semibold mb-4"></h3>
<div className="flex flex-col space-y-2">
<Link to="/" className="text-gray-400 hover:text-white text-sm transition-colors"></Link>
<Link to="/about" className="text-gray-400 hover:text-white text-sm transition-colors"></Link>
<Link to="/works" className="text-gray-400 hover:text-white text-sm transition-colors"></Link>
<Link to="/contact" className="text-gray-400 hover:text-white text-sm transition-colors"></Link>
</div>
</div>
<div>
<h3 className="text-white font-semibold mb-4"></h3>
<div className="flex flex-col space-y-2 text-gray-400 text-sm">
<p>邮箱: info@cyhtechnology.com</p>
<p>: +852 XXXX XXXX</p>
<p>地址: 香港九龙湾XX大厦</p>
</div>
</div>
</div>
<div className="border-t border-white/10 mt-8 pt-8 text-center text-gray-500 text-sm">
<p>&copy; 2024 HK CYH Technology. All rights reserved.</p>
</div>
</div>
</footer>
)
}

View File

@@ -0,0 +1,22 @@
const partners = [
'腾讯', '阿里巴巴', '字节跳动', '华为', '小米',
'百度', '京东', '美团', '网易', '快手',
'B站', '小红书', '滴滴', '拼多多', '携程'
]
export default function MarqueeTicker() {
return (
<div className="overflow-hidden py-8">
<div className="flex animate-marquee">
{[...partners, ...partners].map((partner, index) => (
<div
key={index}
className="flex-shrink-0 mx-8 px-6 py-3 glass-card rounded-full"
>
<span className="text-gray-400 font-medium whitespace-nowrap">{partner}</span>
</div>
))}
</div>
</div>
)
}

109
src/components/Navbar.tsx Normal file
View File

@@ -0,0 +1,109 @@
import { useState, useEffect } from 'react'
import { Link, useLocation } from 'react-router-dom'
const navLinks = [
{ path: '/', label: '首页' },
{ path: '/about', label: '关于我们' },
{ path: '/works', label: '作品展示' },
{ path: '/contact', label: '联系我们' },
]
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false)
const [scrolled, setScrolled] = useState(false)
const location = useLocation()
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 50)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
return (
<nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 ${
scrolled
? 'bg-[#0a0a1a]/90 backdrop-blur-xl shadow-2xl shadow-purple-500/10'
: 'bg-transparent'
}`}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-20">
<Link to="/" className="flex items-center space-x-3 group">
<div className="relative">
<div className="w-12 h-12 gradient-bg-animated rounded-xl flex items-center justify-center transform group-hover:rotate-12 transition-transform duration-300">
<span className="text-white font-bold text-2xl">C</span>
</div>
<div className="absolute inset-0 gradient-bg rounded-xl blur-lg opacity-50 group-hover:opacity-100 transition-opacity" />
</div>
<div>
<span className="text-xl font-bold gradient-text">CYH Technology</span>
<div className="h-0.5 w-0 group-hover:w-full gradient-bg transition-all duration-300" />
</div>
</Link>
<div className="hidden md:flex items-center space-x-1">
{navLinks.map((link) => (
<Link
key={link.path}
to={link.path}
className="relative px-4 py-2 text-sm font-medium transition-colors duration-300 group"
>
<span className={
location.pathname === link.path
? 'text-white'
: 'text-gray-400 group-hover:text-white'
}>
{link.label}
</span>
<div className={`absolute bottom-0 left-1/2 -translate-x-1/2 h-0.5 gradient-bg rounded-full transition-all duration-300 ${
location.pathname === link.path ? 'w-8' : 'w-0 group-hover:w-8'
}`} />
{location.pathname === link.path && (
<div className="absolute inset-0 bg-white/5 rounded-lg" />
)}
</Link>
))}
</div>
<button
onClick={() => setIsOpen(!isOpen)}
className="md:hidden relative w-10 h-10 flex items-center justify-center"
>
<div className={`w-6 h-0.5 bg-white transition-all duration-300 ${
isOpen ? 'rotate-45 translate-y-0' : '-translate-y-1.5'
}`} />
<div className={`w-6 h-0.5 bg-white absolute transition-all duration-300 ${
isOpen ? 'opacity-0' : 'opacity-100'
}`} />
<div className={`w-6 h-0.5 bg-white transition-all duration-300 ${
isOpen ? '-rotate-45 translate-y-0' : 'translate-y-1.5'
}`} />
</button>
</div>
</div>
<div className={`md:hidden transition-all duration-500 overflow-hidden ${
isOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'
}`}>
<div className="bg-[#0a0a1a]/95 backdrop-blur-xl border-t border-white/10 px-4 py-4 space-y-2">
{navLinks.map((link, index) => (
<Link
key={link.path}
to={link.path}
onClick={() => setIsOpen(false)}
className={`block px-4 py-3 rounded-xl transition-all duration-300 ${
location.pathname === link.path
? 'text-white bg-gradient-to-r from-purple-500/20 to-transparent'
: 'text-gray-400 hover:text-white hover:bg-white/5'
}`}
style={{ animationDelay: `${index * 100}ms` }}
>
{link.label}
</Link>
))}
</div>
</div>
</nav>
)
}

View File

@@ -0,0 +1,126 @@
import { useEffect, useRef } from 'react'
interface Particle {
x: number
y: number
size: number
speedX: number
speedY: number
opacity: number
}
export default function ParticleBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
let animationId: number
let particles: Particle[] = []
let mouseX = 0
let mouseY = 0
const resize = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
}
const createParticles = () => {
particles = []
const count = Math.floor((canvas.width * canvas.height) / 15000)
for (let i = 0; i < count; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 2 + 0.5,
speedX: (Math.random() - 0.5) * 0.5,
speedY: (Math.random() - 0.5) * 0.5,
opacity: Math.random() * 0.5 + 0.1
})
}
}
const drawParticles = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
particles.forEach((particle, i) => {
particle.x += particle.speedX
particle.y += particle.speedY
if (particle.x < 0) particle.x = canvas.width
if (particle.x > canvas.width) particle.x = 0
if (particle.y < 0) particle.y = canvas.height
if (particle.y > canvas.height) particle.y = 0
ctx.beginPath()
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2)
ctx.fillStyle = `rgba(102, 126, 234, ${particle.opacity})`
ctx.fill()
particles.forEach((particle2, j) => {
if (i === j) return
const dx = particle.x - particle2.x
const dy = particle.y - particle2.y
const distance = Math.sqrt(dx * dx + dy * dy)
if (distance < 150) {
ctx.beginPath()
ctx.strokeStyle = `rgba(102, 126, 234, ${0.15 * (1 - distance / 150)})`
ctx.lineWidth = 0.5
ctx.moveTo(particle.x, particle.y)
ctx.lineTo(particle2.x, particle2.y)
ctx.stroke()
}
})
const dx = particle.x - mouseX
const dy = particle.y - mouseY
const distance = Math.sqrt(dx * dx + dy * dy)
if (distance < 200) {
const force = (200 - distance) / 200
particle.speedX += (dx / distance) * force * 0.02
particle.speedY += (dy / distance) * force * 0.02
}
particle.speedX *= 0.99
particle.speedY *= 0.99
})
animationId = requestAnimationFrame(drawParticles)
}
const handleMouseMove = (e: MouseEvent) => {
mouseX = e.clientX
mouseY = e.clientY
}
resize()
createParticles()
drawParticles()
window.addEventListener('resize', () => {
resize()
createParticles()
})
window.addEventListener('mousemove', handleMouseMove)
return () => {
cancelAnimationFrame(animationId)
window.removeEventListener('resize', resize)
window.removeEventListener('mousemove', handleMouseMove)
}
}, [])
return (
<canvas
ref={canvasRef}
className="absolute inset-0 pointer-events-none"
style={{ zIndex: 1 }}
/>
)
}

View File

@@ -0,0 +1,159 @@
import { useState } from 'react'
import ScrollReveal from './ScrollReveal'
const steps = [
{
id: 1,
title: '需求沟通',
desc: '深入了解您的品牌、目标受众和传播目标',
detail: '我们会安排专属项目经理与您进行1对1沟通了解视频用途、风格偏好、核心卖点等关键信息并输出详细的需求文档。',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
),
gradient: 'from-purple-500 to-pink-500'
},
{
id: 2,
title: '创意策划',
desc: 'AI辅助生成创意方案快速产出多版脚本',
detail: '基于需求文档我们使用AI快速生成多个创意方案和脚本草稿。您可以从中选择最喜欢的方案或融合多个方案的优点。',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
),
gradient: 'from-blue-500 to-cyan-500'
},
{
id: 3,
title: 'AI生成',
desc: '使用前沿AI技术高效生成视频素材',
detail: '根据确认的脚本我们使用Midjourney、Runway、Stable Diffusion等AI工具生成视频素材包括画面、配音、字幕等。',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
),
gradient: 'from-green-500 to-emerald-500'
},
{
id: 4,
title: '人工精修',
desc: '专业团队优化细节,确保品质达标',
detail: 'AI生成的初稿会由专业后期团队进行精修包括画面调色、节奏调整、特效添加等确保最终效果达到商用标准。',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
),
gradient: 'from-orange-500 to-yellow-500'
},
{
id: 5,
title: '交付验收',
desc: '多轮修改直至满意,提供完整源文件',
detail: '我们提供2-3轮免费修改直至您完全满意。交付内容包括成品视频、源文件、字体版权证明等方便您后续二次编辑。',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
gradient: 'from-pink-500 to-rose-500'
}
]
export default function ProcessAnimation() {
const [activeStep, setActiveStep] = useState(0)
return (
<section className="py-24 px-4 bg-[#0d0d2b]/50">
<div className="max-w-7xl mx-auto">
<ScrollReveal>
<div className="text-center mb-16">
<div className="inline-flex items-center px-4 py-2 rounded-full glass-card mb-6">
<span className="text-purple-400 text-sm font-medium"></span>
</div>
<h2 className="text-4xl md:text-5xl font-bold mb-4">
<span className="gradient-text"></span>
</h2>
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
</p>
</div>
</ScrollReveal>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
{/* Steps Timeline */}
<div className="lg:col-span-5">
<div className="space-y-4">
{steps.map((step, index) => (
<button
key={step.id}
onClick={() => setActiveStep(index)}
className={`w-full text-left p-5 rounded-2xl transition-all duration-300 ${
activeStep === index
? 'glass-card border-purple-500/50'
: 'hover:bg-white/5'
}`}
>
<div className="flex items-start gap-4">
<div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${step.gradient} flex items-center justify-center text-white flex-shrink-0 transition-transform duration-300 ${
activeStep === index ? 'scale-110' : ''
}`}>
{step.icon}
</div>
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-xs text-gray-500"> {step.id}</span>
<h3 className={`font-semibold transition-colors ${
activeStep === index ? 'text-white' : 'text-gray-400'
}`}>
{step.title}
</h3>
</div>
<p className="text-sm text-gray-500">{step.desc}</p>
</div>
</div>
</button>
))}
</div>
</div>
{/* Step Detail */}
<div className="lg:col-span-7">
<div className="glass-card rounded-3xl p-8 md:p-10 h-full">
<div className="flex items-center gap-4 mb-6">
<div className={`w-16 h-16 rounded-2xl bg-gradient-to-br ${steps[activeStep].gradient} flex items-center justify-center text-white`}>
{steps[activeStep].icon}
</div>
<div>
<span className="text-sm text-gray-500"> {steps[activeStep].id}</span>
<h3 className="text-2xl font-bold text-white">{steps[activeStep].title}</h3>
</div>
</div>
<p className="text-gray-400 text-lg leading-relaxed mb-8">
{steps[activeStep].detail}
</p>
{/* Visual Indicator */}
<div className="relative h-2 bg-white/5 rounded-full overflow-hidden">
<div
className={`absolute inset-y-0 left-0 bg-gradient-to-r ${steps[activeStep].gradient} rounded-full transition-all duration-500`}
style={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
/>
</div>
<div className="flex justify-between mt-3 text-sm text-gray-500">
<span></span>
<span>{activeStep + 1}/{steps.length}</span>
<span></span>
</div>
</div>
</div>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,69 @@
import { useEffect, useRef, ReactNode } from 'react'
interface ScrollRevealProps {
children: ReactNode
direction?: 'up' | 'down' | 'left' | 'right'
delay?: number
className?: string
}
export default function ScrollReveal({
children,
direction = 'up',
delay = 0,
className = ''
}: ScrollRevealProps) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setTimeout(() => {
entry.target.classList.add('animate-visible')
}, delay)
observer.disconnect()
}
},
{ threshold: 0.1 }
)
if (ref.current) {
observer.observe(ref.current)
}
return () => observer.disconnect()
}, [delay])
const getInitialTransform = () => {
switch (direction) {
case 'up': return 'translateY(60px)'
case 'down': return 'translateY(-60px)'
case 'left': return 'translateX(-60px)'
case 'right': return 'translateX(60px)'
}
}
return (
<div
ref={ref}
className={className}
style={{
opacity: 0,
transform: getInitialTransform(),
transition: `all 0.8s cubic-bezier(0.4, 0, 0.2, 1) ${delay}ms`
}}
>
{children}
</div>
)
}
const style = document.createElement('style')
style.textContent = `
.animate-visible {
opacity: 1 !important;
transform: translateY(0) translateX(0) !important;
}
`
document.head.appendChild(style)

View File

@@ -0,0 +1,148 @@
import { useState } from 'react'
import ScrollReveal from './ScrollReveal'
const scenes = [
{
id: 'social',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2m-9 0h10m-10 0H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2h-2" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 11v6m-3-3h6" />
</svg>
),
title: '社交媒体',
desc: '抖音、小红书、Instagram等平台爆款内容',
features: ['15秒竖屏视频', '高完播率设计', '爆款文案生成', '多平台适配'],
gradient: 'from-pink-500 to-rose-500'
},
{
id: 'ecommerce',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" />
</svg>
),
title: '电商营销',
desc: '产品展示、直播切片、详情页视频',
features: ['3D产品展示', '卖点可视化', '多角度展示', '批量生成'],
gradient: 'from-orange-500 to-yellow-500'
},
{
id: 'education',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
),
title: '教育培训',
desc: '在线课程、知识科普、企业内训',
features: ['知识可视化', 'AI讲师生成', '课程片头制作', '互动元素'],
gradient: 'from-blue-500 to-cyan-500'
},
{
id: 'brand',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
),
title: '企业宣传',
desc: '品牌故事、企业宣传片、招聘视频',
features: ['品牌形象塑造', '企业文化展示', '数字人代言', '多语言版本'],
gradient: 'from-purple-500 to-violet-500'
}
]
export default function ServiceScenes() {
const [activeScene, setActiveScene] = useState(scenes[0])
return (
<section className="py-24 px-4">
<div className="max-w-7xl mx-auto">
<ScrollReveal>
<div className="text-center mb-16">
<div className="inline-flex items-center px-4 py-2 rounded-full glass-card mb-6">
<span className="text-purple-400 text-sm font-medium"></span>
</div>
<h2 className="text-4xl md:text-5xl font-bold mb-4">
<span className="gradient-text">AI视频</span>
</h2>
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
AI视频解决方案
</p>
</div>
</ScrollReveal>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
{/* Scene Tabs */}
<div className="lg:col-span-4 space-y-3">
{scenes.map((scene) => (
<button
key={scene.id}
onClick={() => setActiveScene(scene)}
className={`w-full text-left p-5 rounded-2xl transition-all duration-300 ${
activeScene.id === scene.id
? 'glass-card border-purple-500/50'
: 'hover:bg-white/5'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${scene.gradient} flex items-center justify-center text-white flex-shrink-0`}>
{scene.icon}
</div>
<div>
<h3 className={`font-semibold transition-colors ${
activeScene.id === scene.id ? 'text-white' : 'text-gray-400'
}`}>
{scene.title}
</h3>
<p className="text-sm text-gray-500">{scene.desc}</p>
</div>
</div>
</button>
))}
</div>
{/* Scene Detail */}
<div className="lg:col-span-8">
<div className="glass-card rounded-3xl p-8 md:p-10">
<div className="flex items-center gap-4 mb-8">
<div className={`w-16 h-16 rounded-2xl bg-gradient-to-br ${activeScene.gradient} flex items-center justify-center text-white`}>
{activeScene.icon}
</div>
<div>
<h3 className="text-2xl font-bold text-white">{activeScene.title}</h3>
<p className="text-gray-400">{activeScene.desc}</p>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
{activeScene.features.map((feature, index) => (
<div key={index} className="flex items-center gap-3 p-4 rounded-xl bg-white/5">
<div className={`w-8 h-8 rounded-lg bg-gradient-to-br ${activeScene.gradient} flex items-center justify-center flex-shrink-0`}>
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
<span className="text-gray-300">{feature}</span>
</div>
))}
</div>
<div className="aspect-video rounded-2xl overflow-hidden bg-gradient-to-br from-purple-900/50 to-blue-900/50 flex items-center justify-center">
<div className="text-center">
<div className="w-20 h-20 mx-auto mb-4 rounded-full gradient-bg-animated flex items-center justify-center">
<svg className="w-10 h-10 text-white ml-1" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z" />
</svg>
</div>
<p className="text-gray-400"> {activeScene.title} </p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,37 @@
interface Testimonial {
name: string
role: string
company: string
content: string
avatar: string
}
interface TestimonialCardProps {
testimonial: Testimonial
}
export default function TestimonialCard({ testimonial }: TestimonialCardProps) {
return (
<div className="glass-card rounded-2xl p-8 hover-lift">
<div className="flex items-center mb-6">
<div className="w-14 h-14 rounded-full overflow-hidden mr-4 border-2 border-purple-500/30">
<img
src={testimonial.avatar}
alt={testimonial.name}
className="w-full h-full object-cover"
/>
</div>
<div>
<h4 className="text-white font-semibold">{testimonial.name}</h4>
<p className="text-gray-400 text-sm">{testimonial.role} · {testimonial.company}</p>
</div>
</div>
<div className="mb-4">
{[...Array(5)].map((_, i) => (
<span key={i} className="text-yellow-400 text-lg"></span>
))}
</div>
<p className="text-gray-300 leading-relaxed">"{testimonial.content}"</p>
</div>
)
}

View File

@@ -0,0 +1,73 @@
import { useState, useRef } from 'react'
import { Video } from '../data/videos'
interface VideoCardProps {
video: Video
onClick: (video: Video) => void
}
export default function VideoCard({ video, onClick }: VideoCardProps) {
const [tilt, setTilt] = useState({ x: 0, y: 0 })
const cardRef = useRef<HTMLDivElement>(null)
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!cardRef.current) return
const rect = cardRef.current.getBoundingClientRect()
const x = (e.clientX - rect.left) / rect.width
const y = (e.clientY - rect.top) / rect.height
setTilt({
x: (y - 0.5) * 10,
y: (x - 0.5) * -10
})
}
const handleMouseLeave = () => {
setTilt({ x: 0, y: 0 })
}
return (
<div
ref={cardRef}
className="group cursor-pointer rounded-2xl overflow-hidden gradient-border glow-on-hover"
onClick={() => onClick(video)}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
style={{
transform: `perspective(1000px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)`,
transition: 'transform 0.3s ease'
}}
>
<div className="relative aspect-video overflow-hidden">
<img
src={video.thumbnail}
alt={video.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-500" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-20 h-20 rounded-full gradient-bg-animated flex items-center justify-center opacity-0 group-hover:opacity-100 transform scale-0 group-hover:scale-100 transition-all duration-500 shadow-2xl shadow-purple-500/50">
<svg className="w-10 h-10 text-white ml-1" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z" />
</svg>
</div>
</div>
<div className="absolute top-4 right-4 px-3 py-1.5 rounded-full text-xs font-semibold gradient-bg-animated shadow-lg">
{video.category}
</div>
<div className="absolute bottom-0 left-0 right-0 h-1 gradient-bg-animated transform scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left" />
</div>
<div className="p-6 bg-[#0d0d2b]">
<h3 className="text-white font-bold text-lg mb-2 group-hover:text-transparent group-hover:bg-clip-text group-hover:bg-gradient-to-r group-hover:from-purple-400 group-hover:to-pink-400 transition-all duration-300">
{video.title}
</h3>
<p className="text-gray-400 text-sm line-clamp-2 group-hover:text-gray-300 transition-colors">
{video.description}
</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,70 @@
import { useEffect, useRef } from 'react'
import { Video } from '../data/videos'
interface VideoModalProps {
video: Video | null
onClose: () => void
}
export default function VideoModal({ video, onClose }: VideoModalProps) {
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
if (video && videoRef.current) {
videoRef.current.play()
}
}, [video])
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', handleEscape)
return () => document.removeEventListener('keydown', handleEscape)
}, [onClose])
if (!video) return null
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
onClick={onClose}
>
<div
className="relative w-full max-w-4xl bg-[#1a1a2e] rounded-2xl overflow-hidden animate-fade-in-up"
onClick={(e) => e.stopPropagation()}
>
<button
onClick={onClose}
className="absolute top-4 right-4 z-10 w-10 h-10 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center transition-colors"
>
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<div className="aspect-video">
<video
ref={videoRef}
src={video.url}
controls
className="w-full h-full"
poster={video.thumbnail}
>
</video>
</div>
<div className="p-6">
<div className="flex items-center gap-3 mb-2">
<h2 className="text-2xl font-bold text-white">{video.title}</h2>
<span className="px-3 py-1 rounded-full text-sm gradient-bg">
{video.category}
</span>
</div>
<p className="text-gray-400">{video.description}</p>
</div>
</div>
</div>
)
}