Files
v0andASOwnerYT 49996fb164 feat: update stats row with accurate Cloudrite info
Replace fake stats with real metrics and adjust grid layout.

Co-authored-by: ASOwnerYT <[email protected]>
2026-03-20 00:27:20 +00:00

218 lines
7.6 KiB
TypeScript

"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&apos;re not a faceless corporation. We&apos;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-3 gap-8 p-8 lg:p-12 bg-card border border-border rounded-3xl">
<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">
<p className="text-5xl md:text-6xl font-bold text-primary">NZ</p>
<p className="text-muted-foreground mt-2">Hosted & Based</p>
</div>
</div>
</div>
</section>
)
}