Files
LynkedUpPro_CRM/src/components/Services.jsx
T

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">&#9655; 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;