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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user