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:
@@ -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 ${
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}`}
|
||||
|
||||
@@ -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'}>
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user