Initial commit from v0
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { Check, MapPin, Clock, Shield, Zap } from "lucide-react"
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: MapPin,
|
||||
title: "Locally Based",
|
||||
description: "We're right here in Auckland. Real people, real support, no overseas call centres."
|
||||
},
|
||||
{
|
||||
icon: Clock,
|
||||
title: "Fast Response",
|
||||
description: "We understand downtime costs money. Our team responds quickly to get you back online."
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
title: "Secure & Reliable",
|
||||
description: "Your data is safe with us. We use enterprise-grade security for all our services."
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
title: "No Jargon",
|
||||
description: "We explain things in plain English. Tech shouldn't be complicated."
|
||||
}
|
||||
]
|
||||
|
||||
const benefits = [
|
||||
"No lock-in contracts",
|
||||
"Free initial consultation",
|
||||
"Transparent pricing",
|
||||
"Ongoing support included",
|
||||
"NZ-based data hosting",
|
||||
"Small business specialists"
|
||||
]
|
||||
|
||||
function AnimatedCounter({ end, suffix = "" }: { end: number; suffix?: string }) {
|
||||
const [count, setCount] = useState(0)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsVisible(true)
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
)
|
||||
|
||||
if (ref.current) {
|
||||
observer.observe(ref.current)
|
||||
}
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVisible) return
|
||||
|
||||
const duration = 2000
|
||||
const steps = 60
|
||||
const increment = end / steps
|
||||
let current = 0
|
||||
|
||||
const timer = setInterval(() => {
|
||||
current += increment
|
||||
if (current >= end) {
|
||||
setCount(end)
|
||||
clearInterval(timer)
|
||||
} else {
|
||||
setCount(Math.floor(current))
|
||||
}
|
||||
}, duration / steps)
|
||||
|
||||
return () => clearInterval(timer)
|
||||
}, [isVisible, end])
|
||||
|
||||
return (
|
||||
<div ref={ref} className="text-5xl md:text-6xl font-bold text-primary">
|
||||
{count}{suffix}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Features() {
|
||||
const [activeFeature, setActiveFeature] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setActiveFeature((prev) => (prev + 1) % features.length)
|
||||
}, 4000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section id="features" className="relative py-32 bg-card/30">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
{/* Section Header */}
|
||||
<div className="text-center mb-20">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 mb-6 border border-border rounded-full bg-card/50">
|
||||
<span className="text-sm text-muted-foreground">Why Cloudrite?</span>
|
||||
</div>
|
||||
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight mb-6">
|
||||
<span className="text-balance">Tech That Actually Works</span>
|
||||
</h2>
|
||||
<p className="max-w-2xl mx-auto text-lg text-muted-foreground">
|
||||
We're not a faceless corporation. We're your friendly neighbourhood
|
||||
I.T team who genuinely cares about your business.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Features Bento Grid */}
|
||||
<div className="grid lg:grid-cols-2 gap-8 mb-20">
|
||||
{/* Left side - Feature showcase */}
|
||||
<div className="relative bg-card border border-border rounded-3xl p-8 lg:p-12 overflow-hidden">
|
||||
{/* Background decoration */}
|
||||
<div className="absolute top-0 right-0 w-64 h-64 bg-primary/10 rounded-full blur-[100px]" />
|
||||
|
||||
{/* Feature icons row */}
|
||||
<div className="flex gap-4 mb-8">
|
||||
{features.map((feature, index) => {
|
||||
const Icon = feature.icon
|
||||
return (
|
||||
<button
|
||||
key={feature.title}
|
||||
onClick={() => setActiveFeature(index)}
|
||||
className={`w-14 h-14 rounded-xl flex items-center justify-center transition-all duration-300 ${
|
||||
activeFeature === index
|
||||
? "bg-primary text-primary-foreground scale-110"
|
||||
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-6 h-6" />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Active feature content */}
|
||||
<div className="relative z-10 min-h-[200px]">
|
||||
{features.map((feature, index) => (
|
||||
<div
|
||||
key={feature.title}
|
||||
className={`transition-all duration-500 ${
|
||||
activeFeature === index
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 translate-y-4 absolute inset-0 pointer-events-none"
|
||||
}`}
|
||||
>
|
||||
<h3 className="text-3xl font-bold mb-4">{feature.title}</h3>
|
||||
<p className="text-xl text-muted-foreground leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Progress bar */}
|
||||
<div className="flex gap-2 mt-8">
|
||||
{features.map((_, index) => (
|
||||
<div key={index} className="h-1 flex-1 bg-border rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full bg-primary transition-all duration-300 ${
|
||||
index === activeFeature ? "animate-progress" : index < activeFeature ? "w-full" : "w-0"
|
||||
}`}
|
||||
style={{
|
||||
width: index === activeFeature ? "100%" : index < activeFeature ? "100%" : "0%",
|
||||
transition: index === activeFeature ? "width 4s linear" : "width 0.3s ease",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Benefits list */}
|
||||
<div className="bg-card border border-border rounded-3xl p-8 lg:p-12">
|
||||
<h3 className="text-2xl font-bold mb-8">What You Get With Us</h3>
|
||||
<div className="space-y-6">
|
||||
{benefits.map((benefit, index) => (
|
||||
<div
|
||||
key={benefit}
|
||||
className="flex items-center gap-4 group"
|
||||
style={{ animationDelay: `${index * 100}ms` }}
|
||||
>
|
||||
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center transition-all duration-300 group-hover:bg-primary group-hover:scale-110">
|
||||
<Check className="w-5 h-5 text-primary transition-colors duration-300 group-hover:text-primary-foreground" />
|
||||
</div>
|
||||
<span className="text-lg font-medium">{benefit}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Row */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8 p-8 lg:p-12 bg-card border border-border rounded-3xl">
|
||||
<div className="text-center">
|
||||
<AnimatedCounter end={100} suffix="+" />
|
||||
<p className="text-muted-foreground mt-2">Businesses Helped</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<AnimatedCounter end={99} suffix="%" />
|
||||
<p className="text-muted-foreground mt-2">Uptime Guarantee</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<AnimatedCounter end={24} suffix="/7" />
|
||||
<p className="text-muted-foreground mt-2">Support Available</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<AnimatedCounter end={10} suffix="+" />
|
||||
<p className="text-muted-foreground mt-2">Years Experience</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user