188 lines
9.5 KiB
React
188 lines
9.5 KiB
React
import React, { useRef, useEffect } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { Icons } from './landing/constants';
|
|
import gsap from 'gsap';
|
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
|
const services = [
|
|
{
|
|
title: "Commercial & Residential Repair and Diagnostics",
|
|
desc: "Leak detection & shingle replacement in record time. We identify micro-fractures invisible to the naked eye using drone-captured LiDAR scans and AI-powered thermal analysis.",
|
|
icon: Icons.Home,
|
|
color: "from-blue-400 to-blue-600",
|
|
glow: "shadow-blue-500/20",
|
|
badge: "Licensed",
|
|
badgeClass: "permit-stamp--verified",
|
|
badgeIcon: "\u2713",
|
|
stat: 5847,
|
|
statLabel: "Properties Inspected",
|
|
},
|
|
{
|
|
title: "Lead & Data Solutions",
|
|
desc: "Advanced lead scoring and real-time storm tracking alerts. Know where the damage is before the phone rings. We direct your reps and marketing into high-impact zones where probability, urgency and opportunity align.",
|
|
icon: Icons.Chart,
|
|
color: "from-purple-400 to-indigo-600",
|
|
glow: "shadow-purple-500/20",
|
|
badge: "Certified",
|
|
badgeClass: "permit-stamp--accent",
|
|
badgeIcon: "\u25B9",
|
|
stat: 96,
|
|
statSuffix: "%",
|
|
statLabel: "Lead Accuracy",
|
|
}
|
|
];
|
|
|
|
const Services = () => {
|
|
const navigate = useNavigate();
|
|
const statRefs = useRef([]);
|
|
|
|
useEffect(() => {
|
|
// Animated Stat Counters
|
|
statRefs.current.forEach((el) => {
|
|
if (!el) return;
|
|
|
|
const target = parseFloat(el.dataset.target);
|
|
const suffix = el.dataset.suffix || '';
|
|
const counter = { value: 0 };
|
|
|
|
el.innerText = '0' + suffix;
|
|
|
|
const formatNumber = (num) => {
|
|
return Math.round(num).toLocaleString('en-US');
|
|
};
|
|
|
|
gsap.to(counter, {
|
|
value: target,
|
|
duration: 2.5,
|
|
ease: "power2.out",
|
|
scrollTrigger: {
|
|
trigger: el,
|
|
start: "top 85%",
|
|
toggleActions: "play none none none",
|
|
},
|
|
onUpdate: () => {
|
|
el.innerText = formatNumber(counter.value) + suffix;
|
|
},
|
|
snap: { value: 1 }
|
|
});
|
|
});
|
|
}, []);
|
|
|
|
const handleMouseMove = (e) => {
|
|
const card = e.currentTarget;
|
|
const rect = card.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
const centerX = rect.width / 2;
|
|
const centerY = rect.height / 2;
|
|
const rotateX = ((y - centerY) / centerY) * -4;
|
|
const rotateY = ((x - centerX) / centerX) * 4;
|
|
card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(1.02)`;
|
|
};
|
|
|
|
const handleMouseLeave = (e) => {
|
|
e.currentTarget.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale(1)';
|
|
};
|
|
|
|
return (
|
|
<section className="py-24 bg-zinc-100 dark:bg-zinc-900 relative overflow-hidden">
|
|
<div className="absolute inset-0 blueprint-grid-fine opacity-25"></div>
|
|
|
|
<div className="max-w-5xl mx-auto px-6 relative z-10">
|
|
<div className="text-center mb-16">
|
|
<span className="permit-stamp permit-stamp--accent mb-6 inline-flex">▷ Core Operations</span>
|
|
<h2 className="text-3xl md:text-5xl font-black text-zinc-900 dark:text-white mb-4 mt-4 tracking-tight">What We Do Best</h2>
|
|
<p className="text-zinc-500 dark:text-zinc-400 max-w-2xl mx-auto">Military-grade technology applied to civilian roofing. Two pillars, one platform.</p>
|
|
</div>
|
|
|
|
<div className="grid md:grid-cols-2 gap-8">
|
|
{services.map((service, idx) => (
|
|
<div
|
|
key={idx}
|
|
onMouseMove={handleMouseMove}
|
|
onMouseLeave={handleMouseLeave}
|
|
className="group relative p-6 md:p-8 rounded-2xl border border-zinc-200 dark:border-white/10 bg-white dark:bg-white/[0.04] hover:bg-zinc-50 dark:hover:bg-white/[0.07] backdrop-blur-sm transition-all duration-200 cursor-pointer overflow-hidden shadow-xl dark:shadow-[0_20px_60px_rgba(0,0,0,0.4)]"
|
|
style={{ transformStyle: 'preserve-3d', transition: 'transform 0.1s ease-out' }}
|
|
>
|
|
{/* Measurement ticks — top */}
|
|
<div className="measure-ticks measure-ticks-top absolute top-0 left-4 right-4 h-3"></div>
|
|
|
|
{/* Gradient glow on hover */}
|
|
<div className={`absolute -inset-0.5 bg-gradient-to-br ${service.color} opacity-0 group-hover:opacity-15 blur-xl transition-opacity duration-300 pointer-events-none`}></div>
|
|
|
|
<div className="relative z-10 flex flex-col h-full">
|
|
{/* Top row: Icon + Service number */}
|
|
<div className="flex items-start justify-between mb-6 mt-1">
|
|
<div className={`w-14 h-14 rounded-xl flex items-center justify-center text-white bg-gradient-to-br ${service.color} shadow-lg ${service.glow} group-hover:scale-110 transition-transform duration-300`}>
|
|
<div className="w-7 h-7 flex items-center justify-center">
|
|
<service.icon />
|
|
</div>
|
|
</div>
|
|
<span className="font-mono text-[11px] font-bold text-zinc-300 dark:text-white/15 tracking-wider">0{idx + 1}</span>
|
|
</div>
|
|
|
|
<h3 className="text-xl font-bold text-zinc-900 dark:text-white mb-3 leading-snug">
|
|
{service.title}
|
|
</h3>
|
|
|
|
<p className="text-zinc-500 dark:text-zinc-400 text-sm leading-relaxed mb-6 flex-grow">
|
|
{service.desc}
|
|
</p>
|
|
|
|
{/* Inline stat with animation */}
|
|
<div className="flex items-baseline gap-2 mb-6">
|
|
<span
|
|
ref={(el) => statRefs.current[idx] = el}
|
|
data-target={service.stat}
|
|
data-suffix={service.statSuffix || ''}
|
|
className="font-mono text-2xl font-bold text-zinc-900 dark:text-white tracking-tight"
|
|
>
|
|
0{service.statSuffix || ''}
|
|
</span>
|
|
<span className="text-xs text-zinc-500 font-medium uppercase tracking-wider">{service.statLabel}</span>
|
|
</div>
|
|
|
|
{/* Footer row: Permit stamp + CTA */}
|
|
<div className="pt-4 border-t border-zinc-100 dark:border-white/5 flex items-center justify-between mt-auto">
|
|
<span className={`permit-stamp ${service.badgeClass}`}>{service.badgeIcon} {service.badge}</span>
|
|
<div
|
|
onClick={(e) => { e.stopPropagation(); navigate('/login'); }}
|
|
className="flex items-center gap-2 text-xs font-bold text-zinc-500 uppercase tracking-wider group-hover:text-blue-400 transition-colors"
|
|
>
|
|
Explore
|
|
<div className="w-6 h-6 rounded-full border border-zinc-300 dark:border-white/10 flex items-center justify-center group-hover:border-blue-400/30 group-hover:rotate-45 transition-all duration-300 text-[10px]">
|
|
↗
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Measurement ticks — bottom */}
|
|
<div className="measure-ticks absolute bottom-0 left-4 right-4 h-3"></div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Trust signals strip */}
|
|
<div className="mt-12 flex flex-wrap items-center justify-center gap-x-8 gap-y-3 text-zinc-500">
|
|
{[
|
|
{ label: "AI-Powered Analysis", icon: "◉" },
|
|
{ label: "15-Min Turnaround", icon: "◉" },
|
|
{ label: "Drone-Powered Inspections", icon: "◉" },
|
|
{ label: "Insurance-Ready Reports", icon: "◉" },
|
|
].map((item, i) => (
|
|
<div key={i} className="flex items-center gap-2 text-xs font-mono uppercase tracking-wider">
|
|
<span className="text-blue-500/60 text-[6px]">{item.icon}</span>
|
|
<span>{item.label}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default Services;
|