feat(ui): optimize CTA buttons with contextual copy and fix static export build issues
CTA Optimization: - Implement scenario-based CTA text across 6 key locations - Add responsive Header CTA with icon+compact design - Enhance CTA Section with vision-driven copy Bug Fixes: - Fix useSearchParams() build failure with dynamic import wrapper - Remove useSearchParams() from PageTransition component - Fix React 19 useEffect lint errors via useSyncExternalStore UI Enhancements: - Add ripple effects and gradient animations to Button - Enhance loading skeleton with branded pulse animation
This commit is contained in:
@@ -32,7 +32,7 @@ export function ChallengeSection() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<section id="challenges" className="py-20 md:py-28 bg-[var(--color-bg-primary)]">
|
||||
<section id="challenges" className="relative py-20 md:py-28 bg-[var(--color-bg-section)]">
|
||||
<div className="container-wide">
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
@@ -56,14 +56,21 @@ export function ChallengeSection() {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 md:gap-8">
|
||||
{CHALLENGES.map((challenge, index) => (
|
||||
<ChallengeCard
|
||||
<motion.div
|
||||
key={challenge.id}
|
||||
title={challenge.title}
|
||||
description={challenge.description}
|
||||
scenario={challenge.scenario}
|
||||
href={challenge.href}
|
||||
index={index}
|
||||
/>
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, x: index === 0 ? -32 : index === 1 ? 0 : 32 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.55, delay: index * 0.12, ease: [0.16, 1, 0.3, 1] }}
|
||||
>
|
||||
<ChallengeCard
|
||||
title={challenge.title}
|
||||
description={challenge.description}
|
||||
scenario={challenge.scenario}
|
||||
href={challenge.href}
|
||||
index={index}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRef, useState } from 'react';
|
||||
import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { ArrowRight, Sparkles } from 'lucide-react';
|
||||
import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||||
|
||||
@@ -19,46 +20,158 @@ interface CTASectionProps {
|
||||
export function CTASection({
|
||||
title = '开启您的数字化转型之旅',
|
||||
description = `与${COMPANY_INFO.shortName}一起,让技术成为您业务增长的核心引擎`,
|
||||
primaryLabel = '立即咨询',
|
||||
primaryLabel = '开启数字化转型之旅',
|
||||
primaryHref = '/contact',
|
||||
secondaryLabel = '了解方案',
|
||||
secondaryHref = '/solutions',
|
||||
}: CTASectionProps) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const mouseX = useMotionValue(0);
|
||||
const mouseY = useMotionValue(0);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const springX = useSpring(mouseX, { stiffness: 80, damping: 20 });
|
||||
const springY = useSpring(mouseY, { stiffness: 80, damping: 20 });
|
||||
|
||||
const glowX = useTransform(springX, [0, 1], ['-10%', '110%']);
|
||||
const glowY = useTransform(springY, [0, 1], ['-10%', '110%']);
|
||||
|
||||
function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) {
|
||||
if (!containerRef.current || shouldReduceMotion) {return;}
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
mouseX.set((e.clientX - rect.left) / rect.width);
|
||||
mouseY.set((e.clientY - rect.top) / rect.height);
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="cta" className="relative py-20 md:py-28 bg-[var(--color-cta-bg)] overflow-hidden">
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0"
|
||||
style={{
|
||||
background: 'linear-gradient(to top, rgba(var(--color-brand-primary-rgb), 0.04) 0%, transparent 60%)',
|
||||
}}
|
||||
/>
|
||||
<section
|
||||
id="cta"
|
||||
ref={containerRef}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className="relative py-24 md:py-32 overflow-hidden"
|
||||
style={{ backgroundColor: 'var(--color-cta-bg)' }}
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<svg className="absolute inset-0 w-full h-full" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<filter id="cta-grain">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.75"
|
||||
numOctaves="4"
|
||||
stitchTiles="stitch"
|
||||
result="noise"
|
||||
/>
|
||||
<feColorMatrix type="saturate" values="0" in="noise" result="desaturated" />
|
||||
<feComponentTransfer in="desaturated" result="faded">
|
||||
<feFuncA type="linear" slope="0.04" />
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
<rect width="100%" height="100%" filter="url(#cta-grain)" />
|
||||
</svg>
|
||||
|
||||
{!shouldReduceMotion && (
|
||||
<>
|
||||
<motion.div
|
||||
className="absolute inset-[-20%]"
|
||||
style={{
|
||||
background:
|
||||
'radial-gradient(circle at center, rgba(196, 30, 58, 0.08) 0%, transparent 50%)',
|
||||
x: glowX,
|
||||
y: glowY,
|
||||
opacity: isHovered ? 1 : 0.5,
|
||||
}}
|
||||
transition={{ opacity: { duration: 0.6 } }}
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.15, 1],
|
||||
opacity: [0.06, 0.12, 0.06],
|
||||
}}
|
||||
transition={{
|
||||
duration: 8,
|
||||
repeat: Infinity,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
className="absolute left-[10%] top-[20%] w-64 h-64 rounded-full"
|
||||
style={{ background: 'radial-gradient(circle, rgba(196,30,58,0.2), transparent 70%)' }}
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.05, 0.1, 0.05],
|
||||
}}
|
||||
transition={{
|
||||
duration: 10,
|
||||
repeat: Infinity,
|
||||
ease: 'easeInOut',
|
||||
delay: 2,
|
||||
}}
|
||||
className="absolute right-[5%] bottom-[25%] w-80 h-80 rounded-full"
|
||||
style={{ background: 'radial-gradient(circle, rgba(196,30,58,0.15), transparent 70%)' }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(to top, rgba(196, 30, 58, 0.06) 0%, transparent 40%), linear-gradient(135deg, transparent 40%, rgba(196, 30, 58, 0.03) 100%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23C41E3A' fill-opacity='0.02'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="container-wide relative z-10">
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-100px' }}
|
||||
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, scale: 0.96, y: 16 }}
|
||||
whileInView={{ opacity: 1, scale: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center max-w-3xl mx-auto"
|
||||
>
|
||||
<h2 className="text-3xl sm:text-4xl font-semibold text-white mb-4">
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: -8 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: 0.15 }}
|
||||
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-white/5 border border-white/10 mb-8"
|
||||
>
|
||||
<Sparkles className="w-3.5 h-3.5 text-[var(--color-brand-primary)]" />
|
||||
<span className="text-sm font-medium text-white/80">开始合作</span>
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-4xl sm:text-5xl lg:text-6xl font-semibold text-white mb-6 tracking-tight leading-tight">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-lg text-white/70 mb-10">
|
||||
<p className="text-lg md:text-xl text-white/65 leading-relaxed mb-12 max-w-2xl mx-auto">
|
||||
{description}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<Button size="lg" asChild>
|
||||
<Button size="lg" asChild className="group relative overflow-hidden">
|
||||
<StaticLink href={primaryHref}>
|
||||
{primaryLabel}
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
<span className="relative z-10 flex items-center">
|
||||
{primaryLabel}
|
||||
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||
</span>
|
||||
</StaticLink>
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" className="border-white/30 text-white hover:bg-white/10 hover:border-white/50" asChild>
|
||||
<StaticLink href={secondaryHref}>
|
||||
{secondaryLabel}
|
||||
</StaticLink>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-white/20 text-white hover:bg-white/10 hover:border-white/35 backdrop-blur-sm"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href={secondaryHref}>{secondaryLabel}</StaticLink>
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useRef, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { HeroInkBackground } from '@/components/ui/hero-ink-background';
|
||||
import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { ArrowRight, MessageSquare, Search, Rocket, Handshake } from 'lucide-react';
|
||||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||||
@@ -51,13 +52,7 @@ export function HeroSectionV2() {
|
||||
aria-labelledby="hero-heading"
|
||||
className="relative min-h-screen flex flex-col justify-center overflow-hidden bg-[var(--color-bg-primary)]"
|
||||
>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
'radial-gradient(ellipse at 15% 40%, rgba(var(--color-brand-primary-rgb), 0.05) 0%, transparent 55%), radial-gradient(ellipse at 85% 25%, rgba(var(--color-primary-rgb), 0.04) 0%, transparent 50%), radial-gradient(ellipse at 50% 90%, rgba(var(--color-brand-primary-rgb), 0.03) 0%, transparent 40%)',
|
||||
}}
|
||||
/>
|
||||
<HeroInkBackground />
|
||||
|
||||
<div className="container-wide py-24 md:py-32 lg:py-40 relative z-10 flex-1 flex items-center">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center w-full">
|
||||
@@ -102,7 +97,7 @@ export function HeroSectionV2() {
|
||||
>
|
||||
<Button size="lg" asChild>
|
||||
<StaticLink href="/contact">
|
||||
立即咨询
|
||||
免费获取定制方案
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
|
||||
@@ -9,7 +9,7 @@ export function ProductMatrixSection() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<section id="products" className="py-20 md:py-28 bg-[var(--color-bg-section)]">
|
||||
<section id="products" className="relative py-16 md:py-20 bg-[var(--color-bg-primary)]">
|
||||
<div className="container-wide">
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
@@ -33,14 +33,21 @@ export function ProductMatrixSection() {
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
|
||||
{PRODUCTS.map((product, index) => (
|
||||
<ProductCard
|
||||
<motion.div
|
||||
key={product.id}
|
||||
title={product.title}
|
||||
description={product.description}
|
||||
href={`/products/${product.id}`}
|
||||
index={index}
|
||||
status={product.status}
|
||||
/>
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 28 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.5, delay: index * 0.08, ease: [0.16, 1, 0.3, 1] }}
|
||||
>
|
||||
<ProductCard
|
||||
title={product.title}
|
||||
description={product.description}
|
||||
href={`/products/${product.id}`}
|
||||
index={index}
|
||||
status={product.status}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@ export function SocialProofSection() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<section id="social-proof" role="region" aria-labelledby="social-proof-heading" className="py-20 md:py-28 bg-[var(--color-bg-section)]">
|
||||
<section id="social-proof" role="region" aria-labelledby="social-proof-heading" className="py-16 md:py-20 bg-[var(--color-bg-section)]">
|
||||
<div className="container-wide">
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
@@ -54,10 +54,10 @@ export function SocialProofSection() {
|
||||
return (
|
||||
<motion.div
|
||||
key={pillar.title}
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-50px' }}
|
||||
transition={{ duration: 0.5, delay: idx * 0.1, ease: [0.16, 1, 0.3, 1] }}
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24, scale: 0.94 }}
|
||||
whileInView={{ opacity: 1, y: 0, scale: 1 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.5, delay: idx * 0.1 + 0.05, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center p-6 md:p-8 rounded-xl bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] hover:border-[rgba(var(--color-brand-primary-rgb),0.3)] transition-colors duration-300"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-xl bg-[var(--color-brand-primary)]/5 flex items-center justify-center mx-auto mb-4">
|
||||
|
||||
Reference in New Issue
Block a user