perf(motion): 第二批入场动效时长收敛至 300ms(4 文件 21 处)
承接 8fb5aae,继续清理 Task #16 的 P0 硬违规(>700ms 明确禁止): - contact-content-v3 (7) / case-detail-page (6) / product-detail-content-v3 (5) / detail-hero (3 处入场) - 审计:P0 54 → 33,P1 163 持平,P2 28 持平 - 只改 duration,保留原 delay(与首批先例一致);改动行已逐行核验为纯动效行 验证: - tsc --noEmit 0 error;eslint 4 个改动文件 0 error - 单测 131 套件 / 1662 通过 / 2 跳过 / EXIT=0(与基线一致) - 视觉基线零变动:reducedMotion+animations disabled 下终态一致, 时长改动不改变最终像素;不带 -u 复跑 92 用例 EXIT=0
This commit is contained in:
@@ -223,7 +223,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.1, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: 0.1, ease: EASE_OUT }}
|
||||
className="text-sm font-mono tracking-[0.15em] text-text-muted mb-8"
|
||||
>
|
||||
{data.heroSubtitle || ''}
|
||||
@@ -232,7 +232,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, delay: 0.2, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: 0.2, ease: EASE_OUT }}
|
||||
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-black text-ink leading-[0.88] tracking-tightest mb-10 sm:mb-12 md:mb-16"
|
||||
>
|
||||
{heroTitleLines.map((line, i) => (
|
||||
@@ -246,7 +246,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
|
||||
style={{ transformOrigin: 'left' }}
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.8, delay: 1.2, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: 1.2, ease: EASE_OUT }}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -260,7 +260,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.5, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: 0.5, ease: EASE_OUT }}
|
||||
className="text-lg md:text-xl text-text-secondary max-w-2xl leading-relaxed mb-12 sm:mb-16"
|
||||
>
|
||||
{data.heroDescription || ''}
|
||||
@@ -278,7 +278,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.8, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, ease: EASE_OUT }}
|
||||
className="lg:col-span-2 space-y-8"
|
||||
>
|
||||
<div>
|
||||
@@ -348,7 +348,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.8, delay: 0.1, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: 0.1, ease: EASE_OUT }}
|
||||
className="lg:col-span-3"
|
||||
>
|
||||
<div className="relative border border-border-primary bg-white p-6 sm:p-8 lg:p-10 h-full overflow-hidden" id="contact-form-section">
|
||||
@@ -513,7 +513,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.8, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, ease: EASE_OUT }}
|
||||
className="max-w-3xl mx-auto text-center"
|
||||
>
|
||||
<div className="text-sm font-mono tracking-[0.2em] text-text-muted uppercase mb-8">
|
||||
|
||||
@@ -32,7 +32,7 @@ function DetailHero({ product }: DetailHeroProps) {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, ease: EASE_OUT }}
|
||||
className="flex items-center gap-4 mb-8"
|
||||
>
|
||||
<div className="w-16 h-16 rounded-2xl bg-brand-soft flex items-center justify-center text-brand">
|
||||
@@ -60,7 +60,7 @@ function DetailHero({ product }: DetailHeroProps) {
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, delay: 0.15, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: 0.15, ease: EASE_OUT }}
|
||||
className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-ink leading-tight tracking-tighter mb-6 sm:mb-8 max-w-4xl"
|
||||
>
|
||||
{product.title}
|
||||
@@ -69,7 +69,7 @@ function DetailHero({ product }: DetailHeroProps) {
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.9, delay: 0.3, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: 0.3, ease: EASE_OUT }}
|
||||
className="text-xl lg:text-2xl text-text-secondary max-w-3xl leading-relaxed mb-8"
|
||||
>
|
||||
{product.description}
|
||||
@@ -78,7 +78,7 @@ function DetailHero({ product }: DetailHeroProps) {
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.9, delay: 0.4, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: 0.4, ease: EASE_OUT }}
|
||||
className="text-lg text-text-muted max-w-3xl leading-relaxed mb-12"
|
||||
>
|
||||
{product.overview}
|
||||
@@ -87,7 +87,7 @@ function DetailHero({ product }: DetailHeroProps) {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.9, delay: 0.55, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.3, delay: 0.55, ease: EASE_OUT }}
|
||||
className="flex flex-wrap gap-6"
|
||||
>
|
||||
<CTAButton href="/contact" dataTestId="product-detail-hero-primary">
|
||||
|
||||
@@ -101,7 +101,7 @@ export function DetailHero({
|
||||
y: 0,
|
||||
filter: 'blur(0px)',
|
||||
transition: {
|
||||
duration: 0.9,
|
||||
duration: 0.3,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
},
|
||||
},
|
||||
@@ -120,7 +120,7 @@ export function DetailHero({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.75,
|
||||
duration: 0.3,
|
||||
delay: 0.25,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
},
|
||||
@@ -189,7 +189,7 @@ export function DetailHero({
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.5, duration: 0.8 }}
|
||||
transition={{ delay: 1.5, duration: 0.3 }}
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2"
|
||||
>
|
||||
<motion.div
|
||||
|
||||
@@ -85,7 +85,7 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
|
||||
className="mb-8"
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.1, ease: EASE }}
|
||||
transition={{ duration: 0.3, delay: 0.1, ease: EASE }}
|
||||
>
|
||||
<div
|
||||
className="font-sans text-[clamp(56px,12vw,120px)] font-black leading-none tracking-[-2px] mb-2"
|
||||
@@ -105,7 +105,7 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
|
||||
className="font-sans text-[clamp(28px,5vw,48px)] font-bold leading-[1.15] tracking-[-0.5px] text-[var(--color-text-primary)] mb-6 max-w-3xl"
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.9, delay: 0.25, ease: EASE }}
|
||||
transition={{ duration: 0.3, delay: 0.25, ease: EASE }}
|
||||
>
|
||||
{data.title}
|
||||
</motion.h1>
|
||||
@@ -115,7 +115,7 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
|
||||
style={{ color: 'var(--color-text-secondary)' }}
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4, ease: EASE }}
|
||||
transition={{ duration: 0.3, delay: 0.4, ease: EASE }}
|
||||
>
|
||||
{data.subtitle}
|
||||
</motion.p>
|
||||
@@ -125,7 +125,7 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
|
||||
className="grid grid-cols-3 gap-6 sm:gap-10 max-w-lg mb-10"
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.55, ease: EASE }}
|
||||
transition={{ duration: 0.3, delay: 0.55, ease: EASE }}
|
||||
>
|
||||
{data.metrics.slice(1, 4).map((m, i) => (
|
||||
<div key={i}>
|
||||
@@ -147,7 +147,7 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
|
||||
className="flex flex-wrap gap-x-6 gap-y-3 text-sm mb-10 border-y border-border-primary py-4"
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.65, ease: EASE }}
|
||||
transition={{ duration: 0.3, delay: 0.65, ease: EASE }}
|
||||
>
|
||||
{data.projectDuration && (
|
||||
<div style={{ color: 'var(--color-text-secondary)' }}>
|
||||
@@ -173,7 +173,7 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
|
||||
className="flex flex-wrap gap-4"
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.8, ease: EASE }}
|
||||
transition={{ duration: 0.3, delay: 0.8, ease: EASE }}
|
||||
>
|
||||
<Button size="lg" asChild>
|
||||
<StaticLink href="/contact">
|
||||
|
||||
Reference in New Issue
Block a user