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:
2026-09-03 21:34:52 +08:00
co-authored by 阿睿
parent ea74bdc134
commit 495f6d4eca
46 changed files with 151 additions and 151 deletions
@@ -134,7 +134,7 @@ function MetricsStrip({ data }: { data: AboutData }) {
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.6, delay: i * 0.08, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: i * 0.08, ease: EASE_OUT }}
className="text-center lg:text-left"
>
<div className={`text-3xl sm:text-4xl md:text-5xl font-black tracking-tight mb-2 ${i === 0 ? 'text-brand' : 'text-ink'}`}>
@@ -264,7 +264,7 @@ function MilestonesSection({ data }: { data: AboutData }) {
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.6, delay: idx * 0.08, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: idx * 0.08, ease: EASE_OUT }}
className="relative pl-8 sm:pl-12 md:pl-16 pb-10 sm:pb-12 md:pb-14 last:pb-0"
>
<div className="absolute left-0 top-2 bottom-0 w-px bg-border-primary last:hidden" />
@@ -75,7 +75,7 @@ export default function BrandContent() {
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: 0.1, ease: EASE_OUT }}
className="mb-8 sm:mb-10"
>
<span className="inline-flex items-center gap-2 px-3 py-1.5 bg-brand/[0.08] border border-brand/20 rounded-full">
@@ -106,7 +106,7 @@ export default function BrandContent() {
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.4, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: 0.4, ease: EASE_OUT }}
className="flex flex-wrap gap-6 sm:gap-10 md:gap-14 mb-10"
>
{brandStats.map((stat, i) => (
@@ -125,7 +125,7 @@ export default function BrandContent() {
<motion.p
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.5, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: 0.5, ease: EASE_OUT }}
className="text-lg sm:text-xl text-text-secondary/80 max-w-2xl leading-relaxed"
>
公司于 2026 年 1 月正式成立,核心团队专注企业数字化,从战略规划到系统落地,致力于陪伴每一家企业实现可衡量的业务改善。
@@ -162,7 +162,7 @@ export default function BrandContent() {
{brandValues.map((item, i) => (
<ScrollReveal key={item.title} delay={i * 0.1}>
<div className="group relative bg-bg-primary border border-border-primary p-8 h-full transition-all duration-300 hover:border-brand/20 hover:shadow-md">
<div className="absolute top-0 left-0 right-0 h-0.5 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left" />
<div className="absolute top-0 left-0 right-0 h-0.5 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
<div className="w-12 h-12 flex items-center justify-center bg-brand/[0.06] text-brand mb-6 transition-colors duration-300 group-hover:bg-brand group-hover:text-white">
<item.icon className="w-5 h-5" />
</div>
@@ -25,10 +25,10 @@ function CaseCard({ caseItem, index }: { caseItem: CaseStudy; index: number }) {
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.6, delay: index * 0.06, ease: EASE_OUT }}
className="group relative block overflow-hidden border border-border-primary hover:border-border-secondary bg-white hover:bg-bg-secondary transition-all duration-500 flex flex-col h-full"
transition={{ duration: 0.3, delay: index * 0.06, ease: EASE_OUT }}
className="group relative block overflow-hidden border border-border-primary hover:border-border-secondary bg-white hover:bg-bg-secondary transition-all duration-150 flex flex-col h-full"
>
<div className="absolute top-0 left-0 h-0 w-[2px] group-hover:h-full transition-all duration-500 origin-top bg-brand" />
<div className="absolute top-0 left-0 h-0 w-[2px] group-hover:h-full transition-all duration-150 origin-top bg-brand" />
<div className="p-6 sm:p-7 lg:p-8 flex flex-col flex-1 relative z-10">
<div className="flex items-center gap-3 mb-5">
@@ -207,7 +207,7 @@ export default function CasesContentV3({
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.6, delay: i * 0.08, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: i * 0.08, ease: EASE_OUT }}
className="text-center lg:text-left"
>
<div className={`text-3xl sm:text-4xl md:text-5xl font-black tracking-tight mb-2 ${
+1 -1
View File
@@ -179,7 +179,7 @@ function HeroSection({ heroData }: {
// reduced-motion 守卫:与 ScrollReveal 同范式——reduce 时传空 initial/animate,
// 元素直接以自然(可见)状态渲染,零位移/淡入;默认(非 reduce,含首帧 null)正常动画。
const rise = (y: number, delay: number, duration = 0.35) => ({
const rise = (y: number, delay: number, duration = 0.3) => ({
initial: reduceMotion ? {} : { opacity: 0, y },
animate: reduceMotion ? {} : { opacity: 1, y: 0 },
transition: { duration: reduceMotion ? 0 : duration, delay: reduceMotion ? 0 : delay, ease: EASE_OUT },
@@ -40,7 +40,7 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) {
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: EASE }}
transition={{ duration: 0.3, ease: EASE }}
className="max-w-4xl"
>
<Badge className="mb-4 bg-[var(--color-brand-bg)] text-[var(--color-brand)] hover:bg-[var(--color-brand)]/20 border-0">
@@ -67,7 +67,7 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) {
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: EASE }}
transition={{ duration: 0.3, ease: EASE }}
className="max-w-4xl"
>
{/* Featured Image */}
@@ -110,7 +110,7 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) {
<img
src={related.image}
alt={related.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-150"
/>
) : (
<div className="w-full h-full bg-gradient-to-br from-[var(--color-brand-bg)] to-[var(--color-bg-tertiary)] flex items-center justify-center">
+4 -4
View File
@@ -23,13 +23,13 @@ function NewsCard({ newsItem, index }: { newsItem: NewsItem; index: number }) {
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.5, delay: index * 0.08, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: index * 0.08, ease: EASE_OUT }}
>
<StaticLink
href={`/news/${newsItem.id}`}
className="group relative block border border-border-primary hover:border-brand/30 bg-white transition-all duration-500 hover:-translate-y-2 overflow-hidden h-full"
className="group relative block border border-border-primary hover:border-brand/30 bg-white transition-all duration-150 hover:-translate-y-2 overflow-hidden h-full"
>
<div className="absolute top-0 left-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-500 origin-top" />
<div className="absolute top-0 left-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-150 origin-top" />
{newsItem.image && !imageError ? (
<div className="aspect-video bg-bg-secondary overflow-hidden">
@@ -37,7 +37,7 @@ function NewsCard({ newsItem, index }: { newsItem: NewsItem; index: number }) {
src={newsItem.image}
alt={newsItem.title}
onError={() => setImageError(true)}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-150"
/>
</div>
) : (
@@ -15,20 +15,20 @@ function RelatedNewsCard({ news, index }: { news: NewsItem; index: number }) {
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.5, delay: index * 0.1, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: index * 0.1, ease: EASE_OUT }}
>
<a
href={`/news/${news.id}`}
className="group relative block border border-border-primary hover:border-brand/30 bg-white transition-all duration-500 hover:-translate-y-2 overflow-hidden h-full"
className="group relative block border border-border-primary hover:border-brand/30 bg-white transition-all duration-150 hover:-translate-y-2 overflow-hidden h-full"
>
<div className="absolute top-0 left-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-500 origin-top" />
<div className="absolute top-0 left-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-150 origin-top" />
<div className="aspect-video bg-bg-secondary overflow-hidden">
{news.image ? (
<img
src={news.image}
alt={news.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-150"
/>
) : (
<div className="w-full h-full bg-gradient-to-br from-brand/[0.08] to-bg-secondary flex items-center justify-center">
@@ -183,9 +183,9 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
{PAIN_POINTS.map((item, index) => (
<div
key={index}
className="group relative border border-border-primary hover:border-brand/30 bg-white p-8 lg:p-10 transition-all duration-500 hover:-translate-y-1 overflow-hidden"
className="group relative border border-border-primary hover:border-brand/30 bg-white p-8 lg:p-10 transition-all duration-150 hover:-translate-y-1 overflow-hidden"
>
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-left" />
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
<div className="w-14 h-14 bg-brand/10 border border-brand/20 flex items-center justify-center mb-6">
<item.icon className="w-7 h-7 text-brand" />
@@ -220,7 +220,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
{UPGRADE_BENEFITS.map((item, index) => (
<div
key={index}
className="text-center p-8 border border-border-primary bg-white hover:border-brand/30 hover:bg-bg-secondary transition-all duration-500 group"
className="text-center p-8 border border-border-primary bg-white hover:border-brand/30 hover:bg-bg-secondary transition-all duration-150 group"
>
<div className="w-12 h-12 mx-auto mb-6 bg-brand/10 border border-brand/20 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<item.icon className="w-6 h-6 text-brand" />
@@ -254,9 +254,9 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
{FEATURE_MODULES.map((item, index) => (
<div
key={index}
className="group relative border border-border-primary hover:border-brand/30 bg-white p-8 transition-all duration-500 hover:-translate-y-1 overflow-hidden"
className="group relative border border-border-primary hover:border-brand/30 bg-white p-8 transition-all duration-150 hover:-translate-y-1 overflow-hidden"
>
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-left" />
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-brand/10 border border-brand/20 flex items-center justify-center shrink-0">
@@ -357,7 +357,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
{certifications.map((cert, index) => (
<div
key={index}
className="group text-center p-8 border border-border-primary bg-white hover:border-brand/30 hover:bg-bg-secondary transition-all duration-500 min-w-[200px]"
className="group text-center p-8 border border-border-primary bg-white hover:border-brand/30 hover:bg-bg-secondary transition-all duration-150 min-w-[200px]"
>
<div className="w-16 h-16 mx-auto mb-4 bg-brand/10 border border-brand/20 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<Shield className="w-8 h-8 text-brand" />
@@ -136,7 +136,7 @@ function FeaturesSection({ features }: FeaturesSectionProps) {
{features.map((feature, idx) => (
<div
key={idx}
className="group relative p-8 bg-bg-primary hover:bg-bg-secondary transition-all duration-500"
className="group relative p-8 bg-bg-primary hover:bg-bg-secondary transition-all duration-150"
>
<div className="flex items-center gap-5">
<div className="w-10 h-10 rounded-xl bg-brand-soft flex items-center justify-center text-brand shrink-0">
@@ -179,7 +179,7 @@ function BenefitsSection({ benefits }: BenefitsSectionProps) {
{benefits.map((benefit, idx) => (
<div
key={idx}
className="group relative p-8 bg-bg-primary hover:bg-bg-secondary transition-all duration-500"
className="group relative p-8 bg-bg-primary hover:bg-bg-secondary transition-all duration-150"
>
<div className="flex items-center gap-6">
<div className="w-12 h-12 rounded-2xl bg-brand-soft/50 flex items-center justify-center text-brand shrink-0">
@@ -232,9 +232,9 @@ function ProcessSection({ process }: ProcessSectionProps) {
{process.map((step, idx) => (
<div
key={idx}
className="group relative text-center p-8 bg-bg-primary hover:bg-bg-secondary transition-all duration-500"
className="group relative text-center p-8 bg-bg-primary hover:bg-bg-secondary transition-all duration-150"
>
<div className="relative z-10 w-20 h-20 rounded-3xl bg-bg-secondary border border-border-primary group-hover:border-brand/50 flex items-center justify-center mx-auto mb-8 transition-all duration-500">
<div className="relative z-10 w-20 h-20 rounded-3xl bg-bg-secondary border border-border-primary group-hover:border-brand/50 flex items-center justify-center mx-auto mb-8 transition-all duration-150">
<span className="text-3xl font-bold text-brand">{idx + 1}</span>
</div>
@@ -67,7 +67,7 @@ function StatItem({ value, suffix, label, start, delay }: { value: number; suffi
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
animate={start ? { opacity: 1, y: 0 } : {}}
transition={{ delay, duration: 0.6, ease: EASE_OUT }}
transition={{ delay, duration: 0.3, ease: EASE_OUT }}
>
<div className="text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-none mb-2">
{count}{suffix}
@@ -171,7 +171,7 @@ function ProductCard({ product, size = 'small' }: { product: Product; size?: 'la
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.6, ease: EASE_OUT }}
transition={{ duration: 0.3, ease: EASE_OUT }}
className="h-full"
>
<StaticLink
@@ -425,7 +425,7 @@ function SuiteCombosSection({ products }: { products: Product[] }) {
<StaticLink
key={idx}
href="/solutions"
className="group p-10 lg:p-12 bg-white hover:bg-bg-secondary transition-all duration-500 overflow-hidden"
className="group p-10 lg:p-12 bg-white hover:bg-bg-secondary transition-all duration-150 overflow-hidden"
>
<div>
<div className="flex items-center gap-4 mb-6">
@@ -445,7 +445,7 @@ function SuiteCombosSection({ products }: { products: Product[] }) {
</span>
</div>
<h3 className="text-xl lg:text-2xl font-bold text-ink mb-3 group-hover:translate-x-1.5 transition-transform duration-500">
<h3 className="text-xl lg:text-2xl font-bold text-ink mb-3 group-hover:translate-x-1.5 transition-transform duration-150">
{combo.label}
</h3>
@@ -74,7 +74,7 @@ export function StandaloneProductClient({ item }: StandaloneProductClientProps)
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
transition={{ duration: 0.3 }}
className="max-w-3xl mx-auto text-center"
>
<div className="mb-6 flex justify-center">
@@ -133,7 +133,7 @@ export function StandaloneProductClient({ item }: StandaloneProductClientProps)
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
transition={{ duration: 0.3 }}
>
<h2 className="mb-6 text-2xl font-bold text-ink">核心能力</h2>
{features.length > 0 ? (
@@ -167,7 +167,7 @@ export function StandaloneProductClient({ item }: StandaloneProductClientProps)
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
transition={{ duration: 0.3 }}
className="mb-10"
>
<div className="flex items-center gap-3 mb-4">
@@ -210,7 +210,7 @@ export function StandaloneProductClient({ item }: StandaloneProductClientProps)
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
transition={{ duration: 0.3 }}
className="mb-10"
>
<div className="flex items-center gap-3 mb-4">
@@ -173,8 +173,8 @@ function FeaturesSection({ service }: { service: Service }) {
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.6, delay: index * 0.06, ease: EASE_OUT }}
className="group relative p-8 sm:p-10 transition-all duration-500 hover:bg-bg-secondary hover:shadow-lg hover:-translate-y-1 bg-bg-primary"
transition={{ duration: 0.3, delay: index * 0.06, ease: EASE_OUT }}
className="group relative p-8 sm:p-10 transition-all duration-150 hover:bg-bg-secondary hover:shadow-lg hover:-translate-y-1 bg-bg-primary"
>
<div className="relative z-10">
<Icon className="w-8 h-8 mb-6 text-brand" />
@@ -227,8 +227,8 @@ function ProcessSection({ service }: { service: Service }) {
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.6, delay: index * 0.08, ease: EASE_OUT }}
className="relative group p-6 sm:p-8 bg-bg-primary hover:bg-bg-secondary hover:shadow-lg hover:-translate-y-1 transition-all duration-500"
transition={{ duration: 0.3, delay: index * 0.08, ease: EASE_OUT }}
className="relative group p-6 sm:p-8 bg-bg-primary hover:bg-bg-secondary hover:shadow-lg hover:-translate-y-1 transition-all duration-150"
>
<div
className="w-2 h-2 rounded-full mb-6"
@@ -263,7 +263,7 @@ function CTASection() {
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.7, ease: EASE_OUT }}
transition={{ duration: 0.3, ease: EASE_OUT }}
className="max-w-3xl"
>
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-extrabold text-ink tracking-tight leading-[0.95] mb-8">
@@ -209,7 +209,7 @@ function ServicesGridSection({ services, pageCopy }: { services?: Service[]; pag
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.6, delay: i * 0.08, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: i * 0.08, ease: EASE_OUT }}
>
<StaticLink
href={`/services/${service.id}`}
@@ -122,9 +122,9 @@ function ChallengesSection({ challenges }: ChallengesSectionProps) {
{challenges.map((challenge, idx) => (
<div
key={idx}
className="group relative p-8 bg-white hover:bg-bg-secondary transition-all duration-500"
className="group relative p-8 bg-white hover:bg-bg-secondary transition-all duration-150"
>
<div className="absolute left-0 top-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-500 origin-top" />
<div className="absolute left-0 top-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-150 origin-top" />
<div className="flex items-start gap-6">
<div className="w-10 h-10 rounded-xl bg-brand-soft/50 flex items-center justify-center text-brand shrink-0 mt-0.5">
<span className="text-lg font-bold">{idx + 1}</span>
@@ -169,9 +169,9 @@ function SolutionsSection({ solutions }: SolutionsSectionProps) {
{solutions.map((solution, idx) => (
<div
key={idx}
className="group relative p-10 bg-white hover:bg-bg-secondary transition-all duration-500"
className="group relative p-10 bg-white hover:bg-bg-secondary transition-all duration-150"
>
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-left" />
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
<div className="flex items-start gap-6">
<div className="w-14 h-14 rounded-2xl bg-brand-soft flex items-center justify-center text-brand shrink-0">
@@ -226,9 +226,9 @@ function ValuePropositionSection({ valueProposition }: ValuePropositionProps) {
return (
<div
key={idx}
className="group relative p-10 text-center bg-white hover:bg-bg-secondary transition-all duration-500"
className="group relative p-10 text-center bg-white hover:bg-bg-secondary transition-all duration-150"
>
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-1/3 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-center" />
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-1/3 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-center" />
<div className="w-20 h-20 rounded-3xl bg-brand-soft flex items-center justify-center text-brand mx-auto mb-8">
{icon}
@@ -279,9 +279,9 @@ function SuiteCombinationSection({ suiteCombination, products }: SuiteCombinatio
{primaryProducts.map((product) => product && (
<div
key={product.id}
className="group relative p-8 bg-white hover:bg-bg-secondary transition-all duration-500 text-center lg:col-span-2"
className="group relative p-8 bg-white hover:bg-bg-secondary transition-all duration-150 text-center lg:col-span-2"
>
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-left" />
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
<div className="w-16 h-16 rounded-2xl bg-brand-soft flex items-center justify-center text-brand mx-auto mb-6">
<Package className="w-8 h-8" />
@@ -303,7 +303,7 @@ function SuiteCombinationSection({ suiteCombination, products }: SuiteCombinatio
</ScrollReveal>
<ScrollReveal delay={0.3} className="lg:col-span-2">
<div className="group relative p-8 bg-brand-soft/10 hover:bg-bg-secondary transition-all duration-500 text-center h-full flex flex-col justify-center">
<div className="group relative p-8 bg-brand-soft/10 hover:bg-bg-secondary transition-all duration-150 text-center h-full flex flex-col justify-center">
<div className="w-16 h-16 rounded-2xl bg-brand-soft flex items-center justify-center text-brand mx-auto mb-6">
<Users className="w-8 h-8" />
</div>
@@ -129,7 +129,7 @@ function SolutionCard({ solution, index, products, featured = false }: { solutio
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.6, delay: index * 0.08, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: index * 0.08, ease: EASE_OUT }}
>
<StaticLink
href={`/solutions/${solution.id}`}
+12 -12
View File
@@ -65,14 +65,14 @@ function StrengthCard({ strength, index }: { strength: StrengthData; index: numb
return (
<motion.div
key={strength.title}
className="group relative border border-border-primary hover:border-brand/30 bg-white transition-all duration-500 overflow-hidden"
className="group relative border border-border-primary hover:border-brand/30 bg-white transition-all duration-150 overflow-hidden"
initial={shouldReduceMotion ? {} : { opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.6, delay: index * 0.08, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: index * 0.08, ease: EASE_OUT }}
whileHover={{ y: -6 }}
>
<div className="absolute top-0 left-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-500 origin-top" />
<div className="absolute top-0 left-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-150 origin-top" />
<div className="p-8 sm:p-10">
<div className="flex items-center gap-4 mb-6">
@@ -88,7 +88,7 @@ function StrengthCard({ strength, index }: { strength: StrengthData; index: numb
<p className="text-text-secondary text-sm leading-relaxed">{strength.description}</p>
</div>
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-brand/40 via-brand/10 to-transparent scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-left" />
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-brand/40 via-brand/10 to-transparent scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
</motion.div>
);
}
@@ -100,14 +100,14 @@ function CultureCard({ culture, index }: { culture: CultureData; index: number }
return (
<motion.div
key={culture.title}
className="group relative border border-border-primary hover:border-brand/30 bg-white transition-all duration-500 text-center overflow-hidden"
className="group relative border border-border-primary hover:border-brand/30 bg-white transition-all duration-150 text-center overflow-hidden"
initial={shouldReduceMotion ? {} : { opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.6, delay: index * 0.1, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: index * 0.1, ease: EASE_OUT }}
whileHover={{ y: -8 }}
>
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-left" />
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
<div className="p-8 sm:p-10 lg:p-12">
<div className="w-16 h-16 mx-auto mb-6 flex items-center justify-center border border-brand/30 bg-brand/[0.08] text-brand">
@@ -120,7 +120,7 @@ function CultureCard({ culture, index }: { culture: CultureData; index: number }
<p className="text-text-secondary leading-relaxed">{culture.description}</p>
</div>
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-brand/20 to-transparent scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-center" />
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-brand/20 to-transparent scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-center" />
</motion.div>
);
}
@@ -152,7 +152,7 @@ export default function TeamContentV3({ data }: { data: Record<string, unknown>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: 0.1, ease: EASE_OUT }}
className="mb-8 sm:mb-10"
>
<span className="inline-flex items-center gap-2 px-3 py-1.5 bg-brand/[0.08] border border-brand/20 rounded-full">
@@ -183,7 +183,7 @@ export default function TeamContentV3({ data }: { data: Record<string, unknown>
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.4, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: 0.4, ease: EASE_OUT }}
className="flex flex-wrap gap-6 sm:gap-10 md:gap-14 mb-10 sm:mb-12"
>
{stats.map((stat, i) => (
@@ -204,7 +204,7 @@ export default function TeamContentV3({ data }: { data: Record<string, unknown>
<motion.p
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.5, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: 0.5, ease: EASE_OUT }}
className="text-lg sm:text-xl text-text-secondary/80 max-w-2xl leading-relaxed mb-12 sm:mb-14"
>
{teamData.heroDescription}
@@ -215,7 +215,7 @@ export default function TeamContentV3({ data }: { data: Record<string, unknown>
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.6, ease: EASE_OUT }}
transition={{ duration: 0.3, delay: 0.6, ease: EASE_OUT }}
className="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-6"
>
<CTAButton href={teamData.heroPrimaryCtaHref || '/contact'}>
+1 -1
View File
@@ -12,7 +12,7 @@ const pageVariants: Variants = {
opacity: 1,
y: 0,
transition: {
duration: 0.35,
duration: 0.3,
ease: [0.16, 1, 0.3, 1] as [number, number, number, number],
},
},