// landing.jsx — responsive (mobile + desktop) + i18n const CDN = '/frontend/assets/'; const REFERENCE_SLIDES = [ { images: [CDN+'reference1-1-9e52dff8.webp', CDN+'reference1-2-4e4525f1.webp', CDN+'reference1-3-f8ca8a9a.webp'], video: CDN+'reference1.mp4' }, { images: [CDN+'referenceChina2-1-fa6ce117.webp', CDN+'referenceChina2-2-6305572a.webp', CDN+'referenceChina2-3-1da23e96.webp'], video: CDN+'referenceChina2.mp4' }, { images: [CDN+'reference3-1-aacd1e93.webp', CDN+'reference3-2-4dcf1a34.webp', CDN+'reference3-3-e48eac56.webp'], video: CDN+'reference3.mp4' }, ]; const I2V_SLIDES = [ { from: CDN+'man-first-b1805e6c.webp', video: CDN+'i2v-man.mp4' }, { from: CDN+'imageChina1-1-59866e85.webp', video: CDN+'i2v-china1.mp4' }, { from: CDN+'image3-1-3ef70758.webp', video: CDN+'i2v-image3.mp4' }, ]; const FEATURE_IMGS = [CDN+'ability1-f6937f83.webp', CDN+'ability2-ce0109cb.webp', CDN+'ability3-fae52eca.webp']; const TEMPLATE_IMGS = [CDN+'template1-a3871cf5.webp', CDN+'template2-d00aa5ec.webp', CDN+'template3-2929203a.webp', CDN+'chineseWomen-578147cd.webp', CDN+'tamplate3-08aa58fc.webp', CDN+'banner5-f5f8e907.webp']; const CASE_IMGS = [CDN+'case-new1.webp', CDN+'creat4-3d0d2761.webp', CDN+'case-new2.webp', CDN+'creat6-fa1a4a32.webp', CDN+'case-new3.webp', CDN+'creat2-9767a973.webp', CDN+'creat3-f3991d9c.webp']; const COMMUNITY_IMGS = [CDN+'image13-0bff0a93.webp', CDN+'image1-65c70b29.webp', CDN+'image2-2bd38261.webp', CDN+'image3-2365f902.webp', CDN+'image5-026301b6.webp', CDN+'image9-7780b44b.webp', CDN+'image7-ad3b6592.webp', CDN+'image15-e2b3524a.webp', CDN+'image11-983b87c9.webp']; const USER_AVATARS = [CDN+'userImageChina1-70ed1a18.webp', CDN+'userImageChina2-a4d679d9.webp', CDN+'userImageChina3-97972b0d.webp', CDN+'userImageChina4-5a2c61f5.webp', CDN+'userImageChina5-d968467b.webp', CDN+'userImageChina6-9a3d5358.webp']; // useCarousel + Carousel are now global from shared.jsx // useBreakpoint() is now global from shared.jsx // ─── Section Badge ──────────────────────────────────────────────────────────── const SectionBadge = ({ text, color = 'var(--accent)' }) => (
{text}
); const SectionTitle = ({ children, size, mb = 16 }) => (

{children}

); // ─── Dot indicators ─────────────────────────────────────────────────────────── const Dots = ({ count, active, onSelect, color = 'var(--accent)' }) => (
{Array.from({ length: count }).map((_, i) => (
); // ─── Reference Section ──────────────────────────────────────────────────────── const ReferenceSection = () => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); const [slide, setSlide] = useCarousel(REFERENCE_SLIDES.length); const videoRefs = React.useRef([]); // Pause/play videos on slide change React.useEffect(() => { videoRefs.current.forEach((v, i) => { if (!v) return; if (i === slide) { v.currentTime = 0; v.play().catch(() => {}); } else { v.pause(); } }); }, [slide]); const visual = (
{/* Left: preload all ref image sets, show active */}
{[0, 1, 2].map(imgIdx => (
{REFERENCE_SLIDES.map((s, si) => ( ))}
{t('landing.ref.refLabel')} {imgIdx + 1}
))}
AI
{/* All videos preloaded, only active one visible */}
{REFERENCE_SLIDES.map((s, i) => (
); return (
{t('landing.ref.title')}

{t('landing.ref.desc1')}

{t('landing.ref.desc2')}

{visual}
); }; // ─── I2V Section ────────────────────────────────────────────────────────────── const I2VSection = () => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); const [slide, setSlide] = useCarousel(I2V_SLIDES.length, 4200); const i2vVideoRefs = React.useRef([]); React.useEffect(() => { i2vVideoRefs.current.forEach((v, i) => { if (!v) return; if (i === slide) { v.currentTime = 0; v.play().catch(() => {}); } else { v.pause(); } }); }, [slide]); const cur = I2V_SLIDES[slide]; const imgPair = (
{/* Preload all first-frame images, show active */} {I2V_SLIDES.map((s, i) => ( ))}
{t('landing.i2v.firstLabel')}
AI
{I2V_SLIDES.map((s, i) => (
); return (
{isMobile ? (
{imgPair}
) : null}
{t('landing.i2v.title')}

{t('landing.i2v.desc1')}

{t('landing.i2v.desc2')}

{['feat1','feat2','feat3'].map(k => (
{t('landing.i2v.'+k)}
))}
{!isMobile ? (
{imgPair}
) : null}
); }; // ─── Why Choose Us ──────────────────────────────────────────────────────────── const WhyUsSection = () => { const { t } = useI18n(); const { isMobile, isTablet } = useBreakpoint(); const [active, setActive] = React.useState(0); React.useEffect(() => { const timer = setInterval(() => setActive(i => (i + 1) % 3), 3500); return () => clearInterval(timer); }, []); return (
{t('landing.why.title')}

{t('landing.why.subtitle')}

{/* Stats */}
{['1','2','3','4'].map(k => (
{t('landing.why.stat'+k+'val')}
{t('landing.why.stat'+k+'label')}
))}
{/* Feature cards */}
{[{k:'1',img:FEATURE_IMGS[0]},{k:'2',img:FEATURE_IMGS[1]},{k:'3',img:FEATURE_IMGS[2]}].map((f,i) => (
setActive(i)} style={{ borderRadius: 18, overflow: 'hidden', border: '1px solid '+(active===i ? 'rgba(34,211,238,0.3)' : 'var(--line-2)'), background: active===i ? 'rgba(34,211,238,0.03)' : 'rgba(255,255,255,0.02)', cursor: 'pointer', transition: 'all .3s' }}>
{ e.target.style.display='none'; e.target.parentNode.style.background='rgba(255,255,255,0.04)'; }} />
{t('landing.why.feat'+f.k+'icon')}
{t('landing.why.feat'+f.k+'title')}
{t('landing.why.feat'+f.k+'desc')}
))}
); }; // ─── Templates ──────────────────────────────────────────────────────────────── const TemplateSection = () => { const { t } = useI18n(); const { isMobile, isTablet } = useBreakpoint(); const cols = isMobile ? 3 : (isTablet ? 4 : 6); return (
{t('landing.tpl.title')}

{t('landing.tpl.subtitle')}

{t('landing.tpl.allBtn')}
{[1,2,3,4,5,6].map((n,i) => ( { e.currentTarget.style.transform='translateY(-3px) scale(1.02)'; e.currentTarget.style.borderColor='rgba(244,114,182,0.4)'; }} onMouseLeave={e => { e.currentTarget.style.transform=''; e.currentTarget.style.borderColor=''; }}> { e.target.style.display='none'; e.target.parentNode.style.background=`hsl(${i*60},40%,10%)`; }} />
{t('landing.tpl.'+n)}
{!isMobile &&
{t('landing.tpl.'+n+'en')}
}
))}
); }; // ─── Cases — uses shared Carousel component ───────────────────────────────── const CasesSection = () => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); const radius = isMobile ? 12 : 18; return (
{t('landing.cases.title')}

{t('landing.cases.subtitle')}

( )} />
); }; // ─── Community ──────────────────────────────────────────────────────────────── const CommunitySection = () => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); return (
{t('landing.comm.title')}

{t('landing.comm.subtitle')}

{t('landing.comm.exploreBtn')}
{COMMUNITY_IMGS.map((img, i) => ( { e.currentTarget.style.transform='scale(1.04)'; e.currentTarget.style.borderColor='rgba(34,211,238,0.3)'; }} onMouseLeave={e => { e.currentTarget.style.transform=''; e.currentTarget.style.borderColor=''; }}> { e.target.style.display='none'; e.target.parentNode.style.background=`hsl(${i*40},30%,10%)`; }} /> ))}
); }; // ─── Testimonials ───────────────────────────────────────────────────────────── const TestimonialsSection = () => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); const pages = isMobile ? 6 : 2; const perPage = isMobile ? 1 : 3; const [slide, setSlide] = useCarousel(pages, 5000); const reviews = [1,2,3,4,5,6].map(n => ({ q: t('landing.rev.q'+n), name: t('landing.rev.n'+n), title: t('landing.rev.t'+n), avatar: USER_AVATARS[n-1] })); return (
{t('landing.rev.title')}
{reviews.map((r, i) => { const inPage = i >= slide * perPage && i < (slide + 1) * perPage; return (
"

{r.q}

{r.name} { e.target.style.display='none'; e.target.parentNode.style.background='rgba(167,139,250,0.15)'; }} />
{r.name}
{r.title}
{[1,2,3,4,5].map(s => )}
); })}
); }; // ─── Pricing (dynamic from API) ────────────────────────────────────────────── const LandingPricing = () => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); const [plans, setPlans] = React.useState(null); const [coins, setCoins] = React.useState(null); const [tab, setTab] = React.useState('plans'); React.useEffect(() => { fetch('/api/v1/subscriptions/plans').then(r => r.json()).then(d => setPlans((d.items || []).sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)))).catch(() => setPlans([])); fetch('/api/v1/subscriptions/coin-packages').then(r => r.json()).then(d => setCoins((d.items || []).sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)))).catch(() => setCoins([])); }, []); // Hide entire section if no data if (plans !== null && coins !== null && plans.length === 0 && coins.length === 0) return null; return (
{t('landing.pricingTitle')}

{t('landing.pricingSubtitle')}

{/* Tab toggle */}
{[{ id: 'plans', label: '订阅套餐' }, { id: 'coins', label: '额度充值' }].map(tb => ( ))}
{tab === 'plans' ? ( /* Subscription plans */
{plans && plans.length > 0 ? (
{plans.map((p, i) => { const isFeatured = i === 1 || p.type === 'month'; const price = p.current_price || p.base_price; const isFree = price === 0; return (
{isFeatured &&
Most popular
}
{p.name}
{isFree ? '$0' : '$' + price} {!isFree && /{p.type === 'year' ? 'year' : p.type === 'week' ? 'week' : 'month'}}
{isFree ? 'Get started free' : isFeatured ? 'Start creating' : 'Subscribe'}
{p.bonus_coins > 0 && (
{p.bonus_coins.toLocaleString()} credits
)} {p.duration_days > 0 && (
{p.duration_days} days access
)} {p.first_purchase_price > 0 && (
First purchase ${p.first_purchase_price}
)}
); })}
) : null}
) : ( /* Coin packages */
{coins && coins.length > 0 ? (
{coins.map(pkg => { const total = pkg.coins + (pkg.bonus_coins || 0); const isPopular = pkg.tag === 'popular'; return (
{pkg.tag &&
{pkg.tag === 'popular' ? 'HOT' : pkg.tag === 'recommended' ? '推荐' : pkg.tag}
}
{total.toLocaleString()}
额度
{pkg.bonus_coins > 0 &&
含赠送 {pkg.bonus_coins.toLocaleString()}
}
${pkg.amount}
购买
); })}
) : null}
)}
{t('landing.pricing.payNote')} {t('landing.pricing.cmpLink')}
); }; // ─── CTA Banner ─────────────────────────────────────────────────────────────── const CTABanner = () => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); return (
e.target.style.display='none'} />

{t('landing.cta.title')}

{t('landing.cta.subtitle')}

{t('landing.cta.mainBtn')} {t('landing.cta.planBtn')}
); }; // ─── FAQ ────────────────────────────────────────────────────────────────────── const FAQSection = () => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); const [open, setOpen] = React.useState(null); return (
{t('landing.faq.title')}
{[1,2,3,4,5,6].map(n => (
{open===n &&
{t('landing.faq.a'+n)}
}
))}
); }; // ─── Hero Prompt Bar ────────────────────────────────────────────────────────── const HeroPromptBar = () => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); const [mode, setMode] = React.useState('image'); const [prompt, setPrompt] = React.useState('cinematic shot of a calico cat astronaut floating above a neon Tokyo, soft volumetric light, 35mm'); return (
{[{id:'image',labelKey:'landing.imgMode',icon:'image'},{id:'video',labelKey:'landing.vidMode',icon:'video',hot:true}].map(m => ( ))} {!isMobile &&
{mode==='image' ? 'Pollo 2.5' : 'Veo 3.1'}
}