feat(content): deepen Bain differentiation across product, solution and service pages
- Replace decorative numbering and side-stripe borders with structured copy - Add scenario positioning, capability metrics and verifiable dimensions - Refine service timelines, deliverables and case-study narratives - Update content constants to support CMS seeding
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
||||
import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
|
||||
import { EASE_OUT } from '@/components/ui/page-decoration';
|
||||
import { CheckCircle2, ChevronDown } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useState } from 'react';
|
||||
@@ -30,7 +30,6 @@ export function MethodologyFramework({
|
||||
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||||
<ScrollReveal className="mb-16 sm:mb-20 max-w-3xl">
|
||||
<SectionLabel>Methodology</SectionLabel>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
@@ -53,13 +52,11 @@ export function MethodologyFramework({
|
||||
transition={{ duration: 0.6, delay: index * 0.08, ease: EASE_OUT }}
|
||||
>
|
||||
<div className="flex gap-6 lg:gap-10">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex flex-col items-center pt-2">
|
||||
<div
|
||||
className="w-12 h-12 rounded-xl flex items-center justify-center text-white font-bold text-lg shrink-0"
|
||||
className="w-3 h-3 rounded-full shrink-0"
|
||||
style={{ backgroundColor: accentColor }}
|
||||
>
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</div>
|
||||
/>
|
||||
{index < layers.length - 1 && (
|
||||
<div className="w-px flex-1 bg-gradient-to-b from-border-primary to-transparent mt-3" />
|
||||
)}
|
||||
@@ -109,7 +106,6 @@ export function TechStackShowcase({ title, subtitle, categories }: TechStackShow
|
||||
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-secondary">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||||
<ScrollReveal className="mb-16 sm:mb-20 max-w-3xl">
|
||||
<SectionLabel>Tech Stack</SectionLabel>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
@@ -125,9 +121,8 @@ 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 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-500"
|
||||
>
|
||||
<div className="absolute top-0 left-0 h-1 w-0 group-hover:w-full transition-all duration-700 bg-brand" />
|
||||
<h3 className="text-lg font-bold text-text-primary mb-5">{category.name}</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{category.items.map((item, i) => (
|
||||
@@ -173,7 +168,6 @@ export function DeliveryTimeline({
|
||||
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||||
<ScrollReveal className="mb-16 sm:mb-20 max-w-3xl">
|
||||
<SectionLabel>Delivery Process</SectionLabel>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
@@ -206,11 +200,8 @@ export function DeliveryTimeline({
|
||||
index % 2 === 0 ? 'lg:ml-12' : 'lg:mr-12'
|
||||
)}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span
|
||||
className="text-xs font-bold tracking-[0.2em] uppercase"
|
||||
style={{ color: accentColor }}
|
||||
>
|
||||
{step.phase}
|
||||
<span className="text-xs font-bold text-text-muted">
|
||||
{`第 ${index + 1} 阶段:${step.phase}`}
|
||||
</span>
|
||||
<span className="text-sm text-text-muted">
|
||||
{step.duration}
|
||||
@@ -221,8 +212,8 @@ export function DeliveryTimeline({
|
||||
{step.description}
|
||||
</p>
|
||||
<div className="pt-5 border-t border-border-primary">
|
||||
<div className="text-xs text-text-muted mb-3 font-medium uppercase tracking-wider">
|
||||
交付物
|
||||
<div className="text-xs text-text-muted mb-3 font-medium">
|
||||
阶段产出
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{step.deliverables.map((deliverable, i) => (
|
||||
@@ -266,7 +257,6 @@ export function FAQSection({ title, subtitle, items }: FAQSectionProps) {
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||||
<div className="grid lg:grid-cols-12 gap-12 lg:gap-20">
|
||||
<ScrollReveal className="lg:col-span-4">
|
||||
<SectionLabel>FAQ</SectionLabel>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
@@ -341,7 +331,6 @@ export function DataProofSection({ title, subtitle, metrics }: DataProofSectionP
|
||||
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||||
<ScrollReveal className="mb-16 sm:mb-20 max-w-3xl">
|
||||
<SectionLabel>Proven Results</SectionLabel>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
@@ -357,9 +346,8 @@ 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 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-500"
|
||||
>
|
||||
<div className="absolute top-0 left-0 h-1 w-0 group-hover:w-full transition-all duration-700 bg-brand" />
|
||||
<div className="relative z-10">
|
||||
<div className="text-5xl lg:text-6xl font-bold tracking-tighter mb-4">
|
||||
<span className="text-text-primary">{metric.value}</span>
|
||||
|
||||
@@ -186,17 +186,6 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
|
||||
transition={{ duration: 0.85, ease: [0.22, 1, 0.36, 1] as const }}
|
||||
className="max-w-3xl mx-auto text-center mb-20"
|
||||
>
|
||||
<motion.span
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.15, duration: 0.5 }}
|
||||
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-brand-soft text-brand text-sm font-bold tracking-wider uppercase mb-5"
|
||||
>
|
||||
<Zap className="w-4 h-4" />
|
||||
核心能力
|
||||
</motion.span>
|
||||
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold tracking-tight text-ink mb-5">
|
||||
为企业打造的<span className="relative inline-block">
|
||||
{product.title}
|
||||
@@ -387,9 +376,6 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
|
||||
className="mt-24"
|
||||
>
|
||||
<div className="text-center mb-14">
|
||||
<span className="inline-block mb-4 text-sm font-bold text-brand tracking-wider uppercase">
|
||||
数据说话
|
||||
</span>
|
||||
<h3 className="text-2xl md:text-3xl font-bold tracking-tight text-ink">
|
||||
用真实效果证明价值
|
||||
</h3>
|
||||
|
||||
@@ -65,7 +65,7 @@ export function DetailTrustSection({
|
||||
caseStudies = [],
|
||||
dataProofs = [],
|
||||
certifications = [],
|
||||
accentColor = '#C41E3A',
|
||||
accentColor: _accentColor = '#C41E3A',
|
||||
}: DetailTrustSectionProps) {
|
||||
const hasContent = caseStudies.length > 0 || dataProofs.length > 0 || certifications.length > 0;
|
||||
if (!hasContent) return null;
|
||||
@@ -85,9 +85,6 @@ export function DetailTrustSection({
|
||||
transition={DESIGN_SYSTEM.effects.scroll.fadeInUp.transition}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<span className="inline-block mb-3 text-sm font-semibold text-brand tracking-wider uppercase">
|
||||
数据说话
|
||||
</span>
|
||||
<h2 className={`${DESIGN_SYSTEM.typography.section.title} text-ink`}>
|
||||
用真实效果证明价值
|
||||
</h2>
|
||||
@@ -135,9 +132,6 @@ export function DetailTrustSection({
|
||||
transition={DESIGN_SYSTEM.effects.scroll.fadeInUp.transition}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<span className="inline-block mb-3 text-sm font-semibold tracking-wider uppercase" style={{ color: accentColor }}>
|
||||
客户案例
|
||||
</span>
|
||||
<h2 className={`${DESIGN_SYSTEM.typography.section.title} text-ink`}>
|
||||
他们已经实现了转型突破
|
||||
</h2>
|
||||
|
||||
@@ -25,10 +25,8 @@ export function ServiceValueSection({ service }: ServiceValueSectionProps) {
|
||||
<section className="relative bg-white py-24 lg:py-32 overflow-hidden">
|
||||
<div className="container-wide relative">
|
||||
<SectionHeader
|
||||
badge="专业服务"
|
||||
title={`为什么选择我们的${service.title}?`}
|
||||
subtitle={service.overview}
|
||||
icon={Zap}
|
||||
/>
|
||||
|
||||
<div className="mt-16">
|
||||
@@ -159,10 +157,8 @@ export function ServiceValueSection({ service }: ServiceValueSectionProps) {
|
||||
}}
|
||||
className="relative flex gap-6 group"
|
||||
>
|
||||
<div className="relative z-10 shrink-0">
|
||||
<div className="w-16 h-16 rounded-full bg-white border-4 border-brand flex items-center justify-center shadow-md group-hover:scale-110 transition-transform duration-300">
|
||||
<span className="text-lg font-bold text-brand">{String(index + 1).padStart(2, '0')}</span>
|
||||
</div>
|
||||
<div className="relative z-10 shrink-0 w-16 flex justify-center">
|
||||
<div className="w-4 h-4 rounded-full bg-brand group-hover:scale-110 transition-transform duration-300" />
|
||||
</div>
|
||||
|
||||
<TiltCard
|
||||
|
||||
@@ -20,14 +20,12 @@ export function SolutionValueSection({ solution }: SolutionValueSectionProps) {
|
||||
<section className="relative bg-white py-24 lg:py-32 overflow-hidden">
|
||||
<div className="container-wide relative">
|
||||
<SectionHeader
|
||||
badge="解决方案"
|
||||
title={
|
||||
<>
|
||||
为<span className="text-brand">{solution.industry}</span>量身定制
|
||||
</>
|
||||
}
|
||||
subtitle={solution.description}
|
||||
icon={Lightbulb}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-10 mt-16">
|
||||
|
||||
@@ -26,6 +26,11 @@ export interface CaseDetailData {
|
||||
timeline: { phase: string; duration: string; description: string }[];
|
||||
services: { id: string; title: string; description: string }[];
|
||||
testimonial?: { quote: string; author: string; role: string };
|
||||
projectDuration?: string;
|
||||
departments?: string[];
|
||||
dataScale?: string;
|
||||
businessProblem?: string;
|
||||
verified?: boolean;
|
||||
}
|
||||
|
||||
interface CaseDetailHeroProps {
|
||||
@@ -60,7 +65,7 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
|
||||
transition={{ duration: 0.6, ease: EASE }}
|
||||
>
|
||||
<span
|
||||
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-[11px] font-medium tracking-wider uppercase"
|
||||
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-xs font-medium"
|
||||
style={{
|
||||
backgroundColor: 'var(--color-brand-soft)',
|
||||
color: 'var(--color-brand)',
|
||||
@@ -136,11 +141,38 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Verifiable dimensions */}
|
||||
<motion.div
|
||||
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 }}
|
||||
>
|
||||
{data.projectDuration && (
|
||||
<div style={{ color: 'var(--color-text-secondary)' }}>
|
||||
<span style={{ color: 'var(--color-text-muted)' }}>项目周期</span> {data.projectDuration}
|
||||
</div>
|
||||
)}
|
||||
{data.departments && data.departments.length > 0 && (
|
||||
<div style={{ color: 'var(--color-text-secondary)' }}>
|
||||
<span style={{ color: 'var(--color-text-muted)' }}>涉及部门</span> {data.departments.join('、')}
|
||||
</div>
|
||||
)}
|
||||
{data.dataScale && (
|
||||
<div style={{ color: 'var(--color-text-secondary)' }}>
|
||||
<span style={{ color: 'var(--color-text-muted)' }}>数据规模</span> {data.dataScale}
|
||||
</div>
|
||||
)}
|
||||
{data.verified && (
|
||||
<div style={{ color: 'var(--color-brand)' }}>✓ 客户授权公开</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="flex flex-wrap gap-4"
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.7, ease: EASE }}
|
||||
transition={{ duration: 0.8, delay: 0.8, ease: EASE }}
|
||||
>
|
||||
<Button size="lg" asChild>
|
||||
<a href="/contact">
|
||||
@@ -172,6 +204,25 @@ function ChallengeSection({ data }: { data: CaseDetailData }) {
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.1} className="lg:col-span-7">
|
||||
{data.businessProblem && (
|
||||
<div
|
||||
className="mb-6 pl-4 border-l-2"
|
||||
style={{ borderColor: 'var(--color-brand)' }}
|
||||
>
|
||||
<div
|
||||
className="text-xs font-medium mb-2"
|
||||
style={{ color: 'var(--color-text-muted)' }}
|
||||
>
|
||||
具体业务问题
|
||||
</div>
|
||||
<p
|
||||
className="text-base md:text-lg leading-relaxed"
|
||||
style={{ color: 'var(--color-text-primary)' }}
|
||||
>
|
||||
{data.businessProblem}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="p-8 sm:p-10 lg:p-12"
|
||||
style={{
|
||||
@@ -181,7 +232,7 @@ function ChallengeSection({ data }: { data: CaseDetailData }) {
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Target className="w-6 h-6" style={{ color: 'var(--color-brand)' }} />
|
||||
<span
|
||||
className="text-sm font-semibold tracking-wider uppercase"
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: 'var(--color-brand)' }}
|
||||
>
|
||||
面临的挑战
|
||||
@@ -224,7 +275,7 @@ function SolutionSection({ data }: { data: CaseDetailData }) {
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Lightbulb className="w-6 h-6" style={{ color: 'var(--color-brand)' }} />
|
||||
<span
|
||||
className="text-sm font-semibold tracking-wider uppercase"
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: 'var(--color-brand)' }}
|
||||
>
|
||||
核心思路
|
||||
@@ -240,7 +291,7 @@ function SolutionSection({ data }: { data: CaseDetailData }) {
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<TrendingUp className="w-6 h-6" style={{ color: 'var(--color-brand)' }} />
|
||||
<span
|
||||
className="text-sm font-semibold tracking-wider uppercase"
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: 'var(--color-brand)' }}
|
||||
>
|
||||
实施路径
|
||||
@@ -336,10 +387,10 @@ function ResultsSection({ data }: { data: CaseDetailData }) {
|
||||
<div className="max-w-[1280px] mx-auto px-5 sm:px-8 lg:px-16 relative z-10">
|
||||
<ScrollReveal className="mb-16 sm:mb-20 text-center max-w-3xl mx-auto">
|
||||
<div
|
||||
className="text-[10px] tracking-[4px] uppercase font-medium mb-4"
|
||||
className="text-xs font-medium mb-4"
|
||||
style={{ color: 'rgba(255,255,255,0.6)' }}
|
||||
>
|
||||
Results
|
||||
项目成果
|
||||
</div>
|
||||
<h2 className="font-sans text-3xl sm:text-4xl md:text-5xl font-bold text-white tracking-tight leading-tight mb-6">
|
||||
可量化的业务成果
|
||||
@@ -431,14 +482,12 @@ function RelatedServicesSection({ services }: { services: CaseDetailData['servic
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<StaggerReveal staggerDelay={0.08}>
|
||||
{services.slice(0, 3).map((s, i) => (
|
||||
{services.slice(0, 3).map((s) => (
|
||||
<ServiceCard
|
||||
key={s.id}
|
||||
number={String(i + 1).padStart(2, '0')}
|
||||
title={s.title}
|
||||
description={s.description}
|
||||
href={`/services/${s.id}`}
|
||||
color="brand"
|
||||
/>
|
||||
))}
|
||||
</StaggerReveal>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ArrowRight } from 'lucide-react';
|
||||
export type ServiceColor = 'brand' | 'blue' | 'teal' | 'amber' | 'purple';
|
||||
|
||||
interface ServiceCardProps {
|
||||
number: string;
|
||||
number?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
color?: ServiceColor;
|
||||
@@ -17,62 +17,26 @@ interface ServiceCardProps {
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
const colorMap: Record<ServiceColor, string> = {
|
||||
brand: 'var(--color-brand)',
|
||||
blue: 'var(--color-accent-blue)',
|
||||
teal: 'var(--color-accent-teal)',
|
||||
amber: 'var(--color-accent-amber)',
|
||||
purple: 'var(--color-accent-purple)',
|
||||
};
|
||||
|
||||
export function ServiceCard({
|
||||
number,
|
||||
number: _number,
|
||||
title,
|
||||
description,
|
||||
color = 'brand',
|
||||
color: _color = 'brand',
|
||||
href,
|
||||
className = '',
|
||||
delay = 0,
|
||||
}: ServiceCardProps) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const accentColor = colorMap[color];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={`group relative bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] rounded-[var(--radius-lg)] overflow-hidden transition-all duration-[var(--transition-normal)] ease-[var(--ease-ink)] hover:-translate-y-1 hover:shadow-[var(--shadow-lg)] hover:border-[var(--color-brand)]/30 ${className}`}
|
||||
className={`group relative bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] rounded-[var(--radius-lg)] overflow-hidden transition-all duration-[var(--transition-normal)] ease-[var(--ease-ink)] hover:-translate-y-1 hover:shadow-[var(--shadow-lg)] hover:border-[var(--color-brand)]/20 ${className}`}
|
||||
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] }}
|
||||
>
|
||||
{/* Left accent bar — grows from bottom on entry */}
|
||||
<motion.div
|
||||
className="absolute left-0 top-0 bottom-0 w-[3px] origin-bottom transition-all duration-[var(--transition-normal)] group-hover:w-1.5"
|
||||
style={{ backgroundColor: accentColor }}
|
||||
initial={shouldReduceMotion ? {} : { scaleY: 0 }}
|
||||
whileInView={{ scaleY: 1 }}
|
||||
viewport={{ once: true, margin: '-40px' }}
|
||||
transition={{ duration: 0.4, delay: delay - 0.1 > 0 ? delay - 0.1 : 0, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
|
||||
<div className="p-6 md:p-8 pl-6 md:pl-8">
|
||||
{/* Large background number */}
|
||||
<div
|
||||
className="absolute top-4 right-6 text-[5rem] font-black leading-none select-none pointer-events-none transition-opacity duration-[var(--transition-normal)] group-hover:opacity-10"
|
||||
style={{ color: 'var(--color-border-light)', opacity: 0.06 }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{number}
|
||||
</div>
|
||||
|
||||
{/* Number label */}
|
||||
<div
|
||||
className="text-xs font-bold tracking-[0.18em] uppercase mb-4"
|
||||
style={{ color: accentColor }}
|
||||
>
|
||||
Service {number}
|
||||
</div>
|
||||
|
||||
<div className="p-6 md:p-8">
|
||||
{/* Title */}
|
||||
<h3 className="text-xl md:text-2xl font-bold mb-3 leading-tight text-[var(--color-text-primary)]">
|
||||
{title}
|
||||
|
||||
@@ -44,11 +44,9 @@ export function ServiceGrid({
|
||||
<div className={`grid grid-cols-1 ${gridCols[columns]} gap-6 md:gap-8`}>
|
||||
{items.map((item, index) => (
|
||||
<ServiceCard
|
||||
key={item.number}
|
||||
number={item.number}
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
color={item.color}
|
||||
href={item.href}
|
||||
delay={index * 0.1}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user