refactor(animations): converge P1 motion durations to ≤300ms target
- 151 scripted + 1 manual duration fixes across 46 files - framer entrance 0.5/0.6s -> 0.3s - Tailwind hover -> duration-150, entrance -> duration-300 - preserve flip-clock/page-transition per semantic decision - design-system.ts dead-code deferred - P1 violations 156 -> 5; P0 remains 0; OK class 249 -> 400 - verified: tsc clean, eslint 0 errors - verified: 130/130 jest suites, 1573 passed - verified: visual regression 46/46 zero-pixel diff Co-Authored-By: 阿睿 <workbuddy@tencent.com>
This commit is contained in:
@@ -49,7 +49,7 @@ export function MethodologyFramework({
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: '-50px' }}
|
||||
transition={{ duration: 0.6, delay: index * 0.08, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: index * 0.08, ease: EASE_OUT }}
|
||||
>
|
||||
<div className="flex gap-6 lg:gap-10">
|
||||
<div className="flex flex-col items-center pt-2">
|
||||
@@ -121,7 +121,7 @@ export function TechStackShowcase({ title, subtitle, categories }: TechStackShow
|
||||
{categories.map((category, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="group relative p-8 border border-border-primary bg-bg-primary hover:border-border-secondary hover:bg-bg-secondary hover:shadow-lg hover:-translate-y-1 transition-all duration-500"
|
||||
className="group relative p-8 border border-border-primary bg-bg-primary hover:border-border-secondary hover:bg-bg-secondary hover:shadow-lg hover:-translate-y-1 transition-all duration-150"
|
||||
>
|
||||
<h3 className="text-lg font-bold text-text-primary mb-5">{category.name}</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -196,7 +196,7 @@ export function DeliveryTimeline({
|
||||
|
||||
<div className="lg:w-1/2 pl-16 lg:pl-0">
|
||||
<div className={cn(
|
||||
'p-8 lg:p-10 border border-border-primary bg-bg-secondary hover:border-border-secondary transition-all duration-500',
|
||||
'p-8 lg:p-10 border border-border-primary bg-bg-secondary hover:border-border-secondary transition-all duration-150',
|
||||
index % 2 === 0 ? 'lg:ml-12' : 'lg:mr-12'
|
||||
)}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
@@ -295,7 +295,7 @@ export function FAQSection({ title, subtitle, items }: FAQSectionProps) {
|
||||
height: openIndex === index ? 'auto' : 0,
|
||||
opacity: openIndex === index ? 1 : 0,
|
||||
}}
|
||||
transition={{ duration: 0.4, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, ease: EASE_OUT }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="px-6 lg:px-7 pb-7 pt-1 text-text-secondary leading-relaxed">
|
||||
@@ -348,7 +348,7 @@ export function DataProofSection({ title, subtitle, metrics }: DataProofSectionP
|
||||
{metrics.map((metric, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="group relative p-10 lg:p-12 bg-bg-secondary hover:bg-bg-secondary hover:shadow-lg transition-all duration-500"
|
||||
className="group relative p-10 lg:p-12 bg-bg-secondary hover:bg-bg-secondary hover:shadow-lg transition-all duration-150"
|
||||
>
|
||||
<div className="relative z-10">
|
||||
<div className="text-5xl lg:text-6xl font-bold tracking-tighter mb-4">
|
||||
|
||||
@@ -50,7 +50,7 @@ export function TestimonialSection({
|
||||
<motion.div
|
||||
className="flex"
|
||||
animate={{ x: `-${activeIndex * 100}%` }}
|
||||
transition={{ duration: 0.6, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, ease: EASE_OUT }}
|
||||
>
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<div key={index} className="w-full shrink-0">
|
||||
@@ -94,7 +94,7 @@ export function TestimonialSection({
|
||||
key={index}
|
||||
onClick={() => setActiveIndex(index)}
|
||||
className={cn(
|
||||
'h-2 rounded-full transition-all duration-500',
|
||||
'h-2 rounded-full transition-all duration-300',
|
||||
activeIndex === index
|
||||
? 'w-8'
|
||||
: 'w-2 bg-border-primary hover:bg-border-secondary'
|
||||
@@ -133,7 +133,7 @@ export function LogoWall({ title, logos }: LogoWallProps) {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.05, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: index * 0.05, ease: EASE_OUT }}
|
||||
className="flex items-center justify-center p-8 bg-bg-primary hover:bg-bg-secondary transition-colors duration-300"
|
||||
>
|
||||
<div className="text-center">
|
||||
|
||||
@@ -14,7 +14,7 @@ export function CaseStudyCard({ study, index }: CaseStudyCardProps) {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-50px' }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
transition={{ duration: 0.3, delay: index * 0.1 }}
|
||||
className="rounded-xl border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md"
|
||||
>
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
|
||||
@@ -37,7 +37,7 @@ export function DetailCTASection({
|
||||
initial={{ opacity: 0, y: 32 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-100px' }}
|
||||
transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }}
|
||||
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="max-w-3xl"
|
||||
>
|
||||
{/* 标签 */}
|
||||
@@ -77,7 +77,7 @@ export function DetailCTASection({
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.5, duration: 0.6 }}
|
||||
transition={{ delay: 0.5, duration: 0.3 }}
|
||||
className="mt-10 text-sm text-white/50"
|
||||
>
|
||||
平均响应时间 < 2小时 · 7×24小时技术支持 · 无需预付费用
|
||||
|
||||
@@ -69,7 +69,7 @@ export function DetailHero({
|
||||
scale: 1,
|
||||
filter: 'blur(0px)',
|
||||
transition: {
|
||||
duration: 0.7,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
},
|
||||
},
|
||||
@@ -136,7 +136,7 @@ export function DetailHero({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.65,
|
||||
duration: 0.3,
|
||||
delay: 0.4,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
},
|
||||
@@ -156,7 +156,7 @@ export function DetailHero({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
duration: 0.3,
|
||||
delay: 0.55,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
},
|
||||
|
||||
@@ -147,7 +147,7 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
duration: 0.3,
|
||||
delay: index * 0.06,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
@@ -188,7 +188,7 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.4,
|
||||
duration: 0.3,
|
||||
delay: index * 0.06,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
@@ -223,7 +223,7 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: 0.3 + index * 0.05,
|
||||
duration: 0.35,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="flex items-start gap-3 text-sm text-text-secondary"
|
||||
|
||||
@@ -78,7 +78,7 @@ export function DetailTrustSection({
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }}
|
||||
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="mb-12"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
@@ -102,7 +102,7 @@ export function DetailTrustSection({
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
duration: 0.3,
|
||||
delay: index * 0.08,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
@@ -123,7 +123,7 @@ export function DetailTrustSection({
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }}
|
||||
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="mb-12"
|
||||
>
|
||||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[0.95]">
|
||||
@@ -139,7 +139,7 @@ export function DetailTrustSection({
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
duration: 0.3,
|
||||
delay: index * 0.08,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
@@ -156,7 +156,7 @@ export function DetailTrustSection({
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }}
|
||||
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="text-center"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-text-secondary mb-6">资质认证</h3>
|
||||
|
||||
@@ -77,7 +77,7 @@ export function ListPageHero({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="max-w-4xl mx-auto text-center"
|
||||
>
|
||||
{/* Dynamic Badge */}
|
||||
@@ -86,7 +86,7 @@ export function ListPageHero({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, scale: 0.95, y: 8 }}
|
||||
whileInView={{ opacity: 1, scale: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.1, duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ delay: 0.1, duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-sm font-medium mb-6 backdrop-blur-sm"
|
||||
style={{ backgroundColor: badgeStyle.bg, color: badgeStyle.text, border: `1px solid ${badgeStyle.border}` }}
|
||||
>
|
||||
@@ -100,7 +100,7 @@ export function ListPageHero({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.15, duration: 0.7, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ delay: 0.15, duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight mb-6 text-[var(--color-text-primary)]"
|
||||
>
|
||||
{title}
|
||||
@@ -111,7 +111,7 @@ export function ListPageHero({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.25, duration: 0.65, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ delay: 0.25, duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-lg md:text-xl max-w-2xl mx-auto leading-relaxed mb-4 text-[var(--color-text-secondary)]"
|
||||
>
|
||||
{subtitle}
|
||||
@@ -123,7 +123,7 @@ export function ListPageHero({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 18 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.35, duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ delay: 0.35, duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-base max-w-xl mx-auto mb-10 text-[var(--color-text-muted)]"
|
||||
>
|
||||
{description}
|
||||
@@ -136,7 +136,7 @@ export function ListPageHero({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.4, duration: 0.7, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ delay: 0.4, duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="flex flex-wrap justify-center gap-8 md:gap-12 mt-12 pt-12 border-t border-[var(--color-border-primary)]"
|
||||
>
|
||||
{stats.map((stat, index) => (
|
||||
@@ -145,7 +145,7 @@ export function ListPageHero({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.5 + index * 0.08, duration: 0.45, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ delay: 0.5 + index * 0.08, duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center min-w-[100px]"
|
||||
>
|
||||
<div className="text-3xl md:text-4xl font-bold tracking-tight text-[var(--color-text-primary)]">
|
||||
|
||||
@@ -24,7 +24,7 @@ export function ProductCard({ product }: ProductCardProps) {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.65,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
}}
|
||||
className="group"
|
||||
@@ -37,7 +37,7 @@ export function ProductCard({ product }: ProductCardProps) {
|
||||
alt={product.title}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-150"
|
||||
/>
|
||||
|
||||
<div className="absolute top-4 left-4 flex gap-2">
|
||||
|
||||
@@ -63,7 +63,7 @@ export function ServiceValueSection({ service }: ServiceValueSectionProps) {
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: index * 0.06,
|
||||
duration: 0.5,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="bain-card p-6"
|
||||
@@ -105,7 +105,7 @@ export function ServiceValueSection({ service }: ServiceValueSectionProps) {
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: index * 0.06,
|
||||
duration: 0.4,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="bain-card p-5 flex items-start gap-3"
|
||||
@@ -135,7 +135,7 @@ export function ServiceValueSection({ service }: ServiceValueSectionProps) {
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: index * 0.08,
|
||||
duration: 0.45,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="bain-card p-5"
|
||||
|
||||
@@ -37,7 +37,7 @@ export function SolutionCard({ solution }: SolutionCardProps) {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.65,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
}}
|
||||
className="group"
|
||||
@@ -50,7 +50,7 @@ export function SolutionCard({ solution }: SolutionCardProps) {
|
||||
src={solution.image}
|
||||
alt={solution.title}
|
||||
fill
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-150"
|
||||
/>
|
||||
) : (
|
||||
<Icon className="w-16 h-16 text-brand/20 group-hover:text-brand/40 transition-colors" />
|
||||
@@ -117,7 +117,7 @@ export function ServiceCard({ service }: ServiceCardProps) {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.65,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
}}
|
||||
className="group"
|
||||
|
||||
@@ -45,7 +45,7 @@ export function SolutionValueSection({ solution }: SolutionValueSectionProps) {
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }}
|
||||
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
||||
>
|
||||
<div className="bain-card p-8">
|
||||
<h3 className="text-xl font-bold text-ink mb-6 flex items-center gap-3">
|
||||
@@ -63,7 +63,7 @@ export function SolutionValueSection({ solution }: SolutionValueSectionProps) {
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: index * 0.08,
|
||||
duration: 0.45,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="flex items-start gap-3 text-text-secondary"
|
||||
@@ -81,7 +81,7 @@ export function SolutionValueSection({ solution }: SolutionValueSectionProps) {
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.7, delay: 0.15, ease: [0.22, 1, 0.36, 1] }}
|
||||
transition={{ duration: 0.3, delay: 0.15, ease: [0.22, 1, 0.36, 1] }}
|
||||
>
|
||||
<div className="bain-card p-8">
|
||||
<h3 className="text-xl font-bold text-ink mb-6 flex items-center gap-3">
|
||||
@@ -99,7 +99,7 @@ export function SolutionValueSection({ solution }: SolutionValueSectionProps) {
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: 0.25 + index * 0.08,
|
||||
duration: 0.45,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="flex items-start gap-3 text-text-secondary"
|
||||
@@ -142,7 +142,7 @@ export function SolutionValueSection({ solution }: SolutionValueSectionProps) {
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: index * 0.08,
|
||||
duration: 0.5,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="bain-card p-7"
|
||||
|
||||
@@ -44,14 +44,14 @@ export function CaseCard({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`group relative overflow-hidden p-6 sm:p-8 md:p-10 lg:p-14 flex flex-col transition-all duration-500 bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] hover:bg-[var(--color-bg-secondary)] ${className}`}
|
||||
className={`group relative overflow-hidden p-6 sm:p-8 md:p-10 lg:p-14 flex flex-col transition-all duration-150 bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] hover:bg-[var(--color-bg-secondary)] ${className}`}
|
||||
>
|
||||
{/* Background image */}
|
||||
{image && (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={{ backgroundImage: `url(${image})` }}
|
||||
className="absolute inset-0 bg-cover bg-center transition-opacity duration-500 opacity-[0.02] group-hover:opacity-[0.05]"
|
||||
className="absolute inset-0 bg-cover bg-center transition-opacity duration-150 opacity-[0.02] group-hover:opacity-[0.05]"
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
|
||||
className="flex flex-wrap items-center gap-3 mb-8"
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, ease: EASE }}
|
||||
transition={{ duration: 0.3, ease: EASE }}
|
||||
>
|
||||
<span
|
||||
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-xs font-medium"
|
||||
@@ -408,7 +408,7 @@ function ResultsSection({ data }: { data: CaseDetailData }) {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className="p-10 sm:p-12 text-center transition-all duration-500 hover:bg-white/[0.05] group relative overflow-hidden"
|
||||
className="p-10 sm:p-12 text-center transition-all duration-150 hover:bg-white/[0.05] group relative overflow-hidden"
|
||||
style={{ backgroundColor: 'var(--color-brand)' }}
|
||||
>
|
||||
{m.highlight && (
|
||||
@@ -416,7 +416,7 @@ function ResultsSection({ data }: { data: CaseDetailData }) {
|
||||
核心指标
|
||||
</div>
|
||||
)}
|
||||
<div className="font-sans tabular-nums text-[clamp(40px,8vw,64px)] font-black leading-none mb-3 text-white transition-transform duration-500 group-hover:scale-110">
|
||||
<div className="font-sans tabular-nums text-[clamp(40px,8vw,64px)] font-black leading-none mb-3 text-white transition-transform duration-150 group-hover:scale-110">
|
||||
{isNumeric && inView && !shouldReduceMotion ? (
|
||||
<AnimatedCounter
|
||||
value={num}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function ChallengeSection() {
|
||||
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] }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="mb-14"
|
||||
>
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
|
||||
@@ -40,7 +40,7 @@ export function CTASection({
|
||||
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] }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center max-w-3xl mx-auto relative"
|
||||
>
|
||||
<div className="absolute -top-8 left-1/2 -translate-x-1/2 hidden md:block">
|
||||
@@ -50,7 +50,7 @@ export function CTASection({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: -8 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: 0.15 }}
|
||||
transition={{ duration: 0.3, delay: 0.15 }}
|
||||
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-[var(--color-brand-soft)] border border-[var(--color-border-primary)] mb-8"
|
||||
>
|
||||
<Sparkles className="w-3.5 h-3.5 text-[var(--color-brand)]" />
|
||||
|
||||
@@ -43,7 +43,7 @@ export function HeroSectionV2() {
|
||||
const fadeUp = (delay: number) => ({
|
||||
initial: shouldReduceMotion ? {} : { opacity: 0, y: 24 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
transition: { duration: 0.5, delay, ease: EASE },
|
||||
transition: { duration: 0.3, delay, ease: EASE },
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -106,7 +106,7 @@ export function HeroSectionV2() {
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 32 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.5, ease: EASE }}
|
||||
transition={{ duration: 0.3, delay: 0.5, ease: EASE }}
|
||||
>
|
||||
<div
|
||||
ref={cardRef}
|
||||
@@ -122,7 +122,7 @@ export function HeroSectionV2() {
|
||||
onMouseLeave={() => setIsCardHovered(false)}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 rounded-2xl pointer-events-none transition-opacity duration-500"
|
||||
className="absolute inset-0 rounded-2xl pointer-events-none transition-opacity duration-300"
|
||||
style={{
|
||||
opacity: isCardHovered ? 1 : 0,
|
||||
background: `radial-gradient(400px circle at ${mousePos.x}px ${mousePos.y}px, rgba(var(--color-brand-rgb), 0.06), transparent 40%)`,
|
||||
|
||||
@@ -25,16 +25,16 @@ export function IndustryGrid({ items, className = '' }: IndustryGridProps) {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className="group relative overflow-hidden py-8 sm:py-10 px-4 sm:px-6 text-center cursor-pointer transition-all duration-400 bg-[var(--color-bg-primary)] hover:bg-[var(--color-bg-secondary)]"
|
||||
className="group relative overflow-hidden py-8 sm:py-10 px-4 sm:px-6 text-center cursor-pointer transition-all duration-150 bg-[var(--color-bg-primary)] hover:bg-[var(--color-bg-secondary)]"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className="relative z-10">
|
||||
<Icon className="w-7 h-7 sm:w-8 sm:h-8 mx-auto mb-2 sm:mb-3 transition-transform duration-400 group-hover:scale-110 text-[var(--color-text-primary)] group-hover:text-[var(--color-brand)]" />
|
||||
<div className="text-[13px] sm:text-[14px] font-medium transition-colors duration-400 text-[var(--color-text-primary)] group-hover:text-[var(--color-brand)]">
|
||||
<Icon className="w-7 h-7 sm:w-8 sm:h-8 mx-auto mb-2 sm:mb-3 transition-transform duration-150 group-hover:scale-110 text-[var(--color-text-primary)] group-hover:text-[var(--color-brand)]" />
|
||||
<div className="text-[13px] sm:text-[14px] font-medium transition-colors duration-150 text-[var(--color-text-primary)] group-hover:text-[var(--color-brand)]">
|
||||
{item.name}
|
||||
</div>
|
||||
<div className="text-[10px] sm:text-[11px] mt-1 sm:mt-1.5 transition-colors duration-400 text-[var(--color-text-subtle)] group-hover:text-[var(--color-text-muted)]">
|
||||
<div className="text-[10px] sm:text-[11px] mt-1 sm:mt-1.5 transition-colors duration-150 text-[var(--color-text-subtle)] group-hover:text-[var(--color-text-muted)]">
|
||||
{item.count}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function InsightCard({
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={{ backgroundImage: `url(${image})` }}
|
||||
className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-105 opacity-10"
|
||||
className="absolute inset-0 bg-cover bg-center transition-transform duration-150 group-hover:scale-105 opacity-10"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -67,7 +67,7 @@ export function InsightCard({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`group p-6 sm:p-8 md:p-10 transition-all duration-400 cursor-pointer flex flex-col bg-[var(--color-bg-primary)] hover:bg-[var(--color-bg-secondary)] ${className}`}
|
||||
className={`group p-6 sm:p-8 md:p-10 transition-all duration-150 cursor-pointer flex flex-col bg-[var(--color-bg-primary)] hover:bg-[var(--color-bg-secondary)] ${className}`}
|
||||
>
|
||||
<div className="text-[10px] tracking-[2px] uppercase font-medium mb-3 md:mb-4"
|
||||
style={{ color: 'var(--color-brand)' }}>
|
||||
|
||||
@@ -19,7 +19,7 @@ export function MethodologySection() {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center max-w-3xl mx-auto mb-14"
|
||||
>
|
||||
<h2 id="methodology-heading" className="text-3xl sm:text-4xl font-semibold text-[var(--color-text-primary)] mb-4">
|
||||
|
||||
@@ -29,12 +29,12 @@ export function ProductCard({
|
||||
return (
|
||||
<StaticLink
|
||||
href={href}
|
||||
className={`group relative block p-6 sm:p-8 lg:p-10 bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] rounded-sm transition-all duration-500 overflow-hidden ${className}`}
|
||||
className={`group relative block p-6 sm:p-8 lg:p-10 bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] rounded-sm transition-all duration-300 overflow-hidden ${className}`}
|
||||
style={{ minHeight: '44px', minWidth: '44px' }}
|
||||
>
|
||||
{/* Left accent bar — visible on hover */}
|
||||
<div
|
||||
className="absolute left-0 top-0 bottom-0 w-[3px] bg-[var(--color-brand)] transition-transform duration-500 origin-top"
|
||||
className="absolute left-0 top-0 bottom-0 w-[3px] bg-[var(--color-brand)] transition-transform duration-300 origin-top"
|
||||
style={{ transform: 'scaleY(0)' }}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'scaleY(1)';
|
||||
@@ -44,7 +44,7 @@ export function ProductCard({
|
||||
{/* Content */}
|
||||
<div className="relative z-10">
|
||||
{/* Icon: 装饰性图标,对屏幕阅读器隐藏 */}
|
||||
<div className="w-10 h-10 sm:w-12 sm:h-12 rounded-lg bg-[var(--color-brand-bg)] flex items-center justify-center mb-4 sm:mb-5 transition-transform duration-500 group-hover:scale-110" aria-hidden="true">
|
||||
<div className="w-10 h-10 sm:w-12 sm:h-12 rounded-lg bg-[var(--color-brand-bg)] flex items-center justify-center mb-4 sm:mb-5 transition-transform duration-150 group-hover:scale-110" aria-hidden="true">
|
||||
{icon}
|
||||
</div>
|
||||
|
||||
@@ -73,9 +73,9 @@ export function ProductCard({
|
||||
</div>
|
||||
|
||||
{/* Hover background lift */}
|
||||
<div className="absolute inset-0 bg-[var(--color-bg-hover)] opacity-0 transition-opacity duration-500 pointer-events-none" />
|
||||
<div className="absolute inset-0 bg-[var(--color-bg-hover)] opacity-0 transition-opacity duration-150 pointer-events-none" />
|
||||
<div
|
||||
className="absolute inset-0 opacity-0 transition-opacity duration-500 pointer-events-none"
|
||||
className="absolute inset-0 opacity-0 transition-opacity duration-300 pointer-events-none"
|
||||
onMouseEnter={(e) => { e.currentTarget.style.opacity = '1'; }}
|
||||
/>
|
||||
</StaticLink>
|
||||
|
||||
@@ -17,7 +17,7 @@ export function ProductMatrixSection() {
|
||||
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] }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="flex flex-col md:flex-row md:items-end md:justify-between gap-4 mb-14"
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -27,7 +27,7 @@ export function QuestionCard({
|
||||
className={`group md:grid md:grid-cols-2 border-b border-[var(--color-border-primary)] relative overflow-hidden ${className}`}
|
||||
>
|
||||
{/* Left accent bar — visible on hover */}
|
||||
<div className="absolute left-0 top-0 bottom-0 w-[3px] hidden md:block transition-transform duration-500 origin-top bg-[var(--color-brand)]"
|
||||
<div className="absolute left-0 top-0 bottom-0 w-[3px] hidden md:block transition-transform duration-300 origin-top bg-[var(--color-brand)]"
|
||||
style={{ transform: 'scaleY(0)' }}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.transform = 'scaleY(1)'; }}
|
||||
/>
|
||||
|
||||
@@ -35,7 +35,7 @@ export function SectionHeader({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-40px' }}
|
||||
transition={{ duration: 0.6, ease: EASE }}
|
||||
transition={{ duration: 0.3, ease: EASE }}
|
||||
>
|
||||
{/* Eyebrow label with brand accent bar */}
|
||||
<div className="flex items-center gap-3 mb-4 md:mb-5">
|
||||
@@ -51,7 +51,7 @@ export function SectionHeader({
|
||||
initial={shouldReduceMotion ? {} : { scaleY: 0 }}
|
||||
whileInView={{ scaleY: 1 }}
|
||||
viewport={{ once: true, margin: '-40px' }}
|
||||
transition={{ duration: 0.4, delay: 0.1, ease: EASE }}
|
||||
transition={{ duration: 0.3, delay: 0.1, ease: EASE }}
|
||||
/>
|
||||
<motion.div
|
||||
className="text-[10px] tracking-[4px] uppercase font-medium"
|
||||
@@ -63,7 +63,7 @@ export function SectionHeader({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, x: -8 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: '-40px' }}
|
||||
transition={{ duration: 0.4, delay: 0.2, ease: EASE }}
|
||||
transition={{ duration: 0.3, delay: 0.2, ease: EASE }}
|
||||
>
|
||||
{label}
|
||||
</motion.div>
|
||||
|
||||
@@ -34,7 +34,7 @@ export function ServiceCard({
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-40px' }}
|
||||
transition={{ duration: 0.6, delay, ease: [0.22, 1, 0.36, 1] }}
|
||||
transition={{ duration: 0.3, delay, ease: [0.22, 1, 0.36, 1] }}
|
||||
>
|
||||
<div className="p-6 md:p-8">
|
||||
{/* Title */}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function SocialProofSection() {
|
||||
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] }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center max-w-3xl mx-auto mb-16"
|
||||
>
|
||||
<h2 id="social-proof-heading" className="text-3xl sm:text-4xl font-semibold text-[var(--color-text-primary)] mb-4">
|
||||
@@ -56,7 +56,7 @@ export function SocialProofSection() {
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="grid grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8 mb-16"
|
||||
>
|
||||
{METRICS.map((metric, idx) => {
|
||||
@@ -67,7 +67,7 @@ export function SocialProofSection() {
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.5, delay: idx * 0.1, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ duration: 0.3, delay: idx * 0.1, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center group"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-full bg-[var(--color-brand)]/5 flex items-center justify-center mx-auto mb-4 group-hover:bg-[var(--color-brand)]/10 transition-colors duration-300">
|
||||
@@ -95,7 +95,7 @@ export function SocialProofSection() {
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.5, delay: 0.2 + idx * 0.1, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ duration: 0.3, delay: 0.2 + idx * 0.1, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="relative p-6 md:p-8 rounded-2xl bg-[var(--color-bg-section)] border border-[var(--color-border-primary)] hover:border-[rgba(var(--color-brand-rgb),0.2)] transition-all duration-300"
|
||||
>
|
||||
<Quote className="w-8 h-8 text-[var(--color-brand)]/15 mb-4" strokeWidth={1.5} />
|
||||
|
||||
@@ -59,7 +59,7 @@ export function WhyUsSection() {
|
||||
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] }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="mb-14"
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-[var(--color-brand-bg)] border border-[var(--color-brand)]/10 mb-4">
|
||||
@@ -83,7 +83,7 @@ export function WhyUsSection() {
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24, scale: 0.96 }}
|
||||
whileInView={{ opacity: 1, y: 0, scale: 1 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.5, delay: idx * 0.08, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ duration: 0.3, delay: idx * 0.08, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="relative group p-6 md:p-8 rounded-2xl bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] hover:border-[rgba(var(--color-brand-rgb),0.25)] transition-all duration-300"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
@@ -134,7 +134,7 @@ export function WhyUsSection() {
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 16 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.5, delay: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ duration: 0.3, delay: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center"
|
||||
>
|
||||
<p className="text-sm text-[var(--color-text-muted)] mb-6">想深入了解我们的团队能力?</p>
|
||||
@@ -151,7 +151,7 @@ export function WhyUsSection() {
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 16 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.5, delay: 0.4, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ duration: 0.3, delay: 0.4, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="mt-16 pt-10 border-t border-[var(--color-border-primary)]"
|
||||
>
|
||||
<p className="text-xs text-center text-[var(--color-text-subtle)] mb-6 tracking-wide uppercase">技术伙伴 / 技术栈</p>
|
||||
|
||||
@@ -92,7 +92,7 @@ export function BrandStamp({ children }: { children: React.ReactNode }) {
|
||||
initial={shouldReduceMotion ? {} : { scale: 1.3, opacity: 0, rotate: -12 }}
|
||||
whileInView={{ scale: 1, opacity: 1, rotate: -5 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="inline-block px-3 py-1.5 border-2 border-[var(--color-brand)] rounded-sm"
|
||||
style={{
|
||||
transform: 'rotate(-5deg)',
|
||||
|
||||
@@ -72,7 +72,7 @@ export function StatsShowcase({
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
'relative p-8 sm:p-10 md:p-12 text-center transition-all duration-500 group',
|
||||
'relative p-8 sm:p-10 md:p-12 text-center transition-all duration-300 group',
|
||||
isDark ? 'bg-dark-bg hover:bg-dark-bg-secondary' : 'bg-bg-primary hover:bg-bg-hover'
|
||||
)}
|
||||
>
|
||||
@@ -131,7 +131,7 @@ export function StatsShowcase({
|
||||
|
||||
{item.accent && (
|
||||
<div
|
||||
className="absolute top-0 left-1/2 -translate-x-1/2 w-12 h-0.5 opacity-0 group-hover:opacity-100 transition-opacity duration-500"
|
||||
className="absolute top-0 left-1/2 -translate-x-1/2 w-12 h-0.5 opacity-0 group-hover:opacity-100 transition-opacity duration-150"
|
||||
style={{ backgroundColor: item.accent }}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user