303 lines
15 KiB
TypeScript
303 lines
15 KiB
TypeScript
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
import { motion, useMotionTemplate, useMotionValue, useReducedMotion } from 'framer-motion';
|
|
import { Link } from 'react-router-dom';
|
|
import gsap from 'gsap';
|
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
import SEO from '../../components/SEO';
|
|
import Breadcrumb from '../../components/Breadcrumb';
|
|
import Services from '../../components/Services';
|
|
import CTA from '../../components/CTA';
|
|
import FAQ from '../../components/FAQ';
|
|
import AreasWeServe from '../../components/AreasWeServe';
|
|
import { ServiceData } from '../data/seoData';
|
|
import heroBg from '../assets/hero-bg.webp';
|
|
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
|
interface ServicePageProps {
|
|
data: ServiceData;
|
|
}
|
|
|
|
const ServicePage: React.FC<ServicePageProps> = ({ data }) => {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const parallaxWrapperRef = useRef<HTMLDivElement>(null);
|
|
const mouseX = useMotionValue(0);
|
|
const mouseY = useMotionValue(0);
|
|
const prefersReducedMotion = useReducedMotion();
|
|
const [isInteractive, setIsInteractive] = useState(false);
|
|
const maskImage = useMotionTemplate`radial-gradient(100px circle at ${mouseX}px ${mouseY}px, black, transparent)`;
|
|
const webkitMaskImage = useMotionTemplate`radial-gradient(100px circle at ${mouseX}px ${mouseY}px, black, transparent)`;
|
|
|
|
useEffect(() => {
|
|
if (prefersReducedMotion || typeof window === 'undefined') {
|
|
setIsInteractive(false);
|
|
return;
|
|
}
|
|
|
|
const mediaQuery = window.matchMedia('(pointer: fine) and (hover: hover)');
|
|
const updateState = () => setIsInteractive(mediaQuery.matches);
|
|
|
|
updateState();
|
|
|
|
if (typeof mediaQuery.addEventListener === 'function') {
|
|
mediaQuery.addEventListener('change', updateState);
|
|
return () => mediaQuery.removeEventListener('change', updateState);
|
|
}
|
|
|
|
mediaQuery.addListener(updateState);
|
|
return () => mediaQuery.removeListener(updateState);
|
|
}, [prefersReducedMotion]);
|
|
|
|
const handleMouseMove = ({ currentTarget, clientX, clientY }: React.MouseEvent) => {
|
|
if (!isInteractive) return;
|
|
const { left, top } = currentTarget.getBoundingClientRect();
|
|
mouseX.set(clientX - left);
|
|
mouseY.set(clientY - top + 75);
|
|
};
|
|
|
|
useLayoutEffect(() => {
|
|
if (!isInteractive) {
|
|
return;
|
|
}
|
|
|
|
const ctx = gsap.context(() => {
|
|
// Parallax Background
|
|
if (parallaxWrapperRef.current) {
|
|
gsap.to(parallaxWrapperRef.current, {
|
|
yPercent: 30,
|
|
ease: "none",
|
|
scrollTrigger: {
|
|
trigger: containerRef.current,
|
|
start: "top top",
|
|
end: "bottom top",
|
|
scrub: true
|
|
}
|
|
});
|
|
}
|
|
|
|
// Text Stagger Animation
|
|
gsap.fromTo(".hero-stagger",
|
|
{ y: 50, opacity: 0 },
|
|
{ y: 0, opacity: 1, duration: 1, stagger: 0.2, ease: "power3.out", delay: 0.2 }
|
|
);
|
|
}, containerRef);
|
|
|
|
return () => ctx.revert();
|
|
}, [isInteractive]);
|
|
|
|
useEffect(() => {
|
|
window.scrollTo(0, 0);
|
|
}, []);
|
|
|
|
const schema = {
|
|
"@context": "https://schema.org",
|
|
"@type": "Service",
|
|
"name": data.h1,
|
|
"description": data.description,
|
|
"url": `https://bayareait.services/${data.slug}`,
|
|
"provider": {
|
|
"@type": "LocalBusiness",
|
|
"name": "Bay Area Affiliates",
|
|
"url": "https://bayareait.services",
|
|
"telephone": "+1-361-765-8400",
|
|
"address": {
|
|
"@type": "PostalAddress",
|
|
"addressLocality": "Corpus Christi",
|
|
"addressRegion": "TX",
|
|
"addressCountry": "US"
|
|
}
|
|
},
|
|
"areaServed": [
|
|
{ "@type": "City", "name": "Corpus Christi" },
|
|
{ "@type": "City", "name": "Portland" },
|
|
{ "@type": "City", "name": "Rockport" },
|
|
{ "@type": "City", "name": "Aransas Pass" },
|
|
{ "@type": "City", "name": "Kingsville" }
|
|
]
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<SEO
|
|
title={data.title}
|
|
description={data.description}
|
|
keywords={data.keywords}
|
|
canonicalUrl={`https://bayareait.services/${data.slug}`}
|
|
schema={schema}
|
|
/>
|
|
|
|
<div className="min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
|
|
{/* Hero Section */}
|
|
<section
|
|
ref={containerRef}
|
|
onMouseMove={isInteractive ? handleMouseMove : undefined}
|
|
className="relative flex min-h-[32rem] items-center justify-center overflow-hidden pt-24 pb-14 md:min-h-[36rem] md:pt-28 md:pb-16 lg:min-h-[40rem] lg:pt-32 lg:pb-20 group"
|
|
>
|
|
{/* Parallax Background */}
|
|
<div className="absolute inset-0 z-0 pointer-events-none">
|
|
<div ref={parallaxWrapperRef} className="absolute w-full h-[120%] -top-[10%] left-0">
|
|
{/* Base Layer */}
|
|
<img
|
|
alt="Abstract dark technology background"
|
|
className="w-full h-full object-cover opacity-90 dark:opacity-70 brightness-75 contrast-150"
|
|
src={heroBg}
|
|
loading="eager"
|
|
decoding="async"
|
|
fetchPriority="high"
|
|
/>
|
|
|
|
{isInteractive && (
|
|
<motion.img
|
|
style={{ maskImage, WebkitMaskImage: webkitMaskImage }}
|
|
alt=""
|
|
aria-hidden="true"
|
|
className="absolute inset-0 w-full h-full object-cover mix-blend-screen opacity-100 brightness-150 contrast-150 filter saturate-150"
|
|
src={heroBg}
|
|
loading="lazy"
|
|
decoding="async"
|
|
/>
|
|
)}
|
|
</div>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-background-light via-transparent to-transparent dark:from-background-dark dark:via-transparent dark:to-transparent"></div>
|
|
<div className="absolute inset-0 bg-gradient-to-b from-background-light/50 dark:from-background-dark/50 to-transparent"></div>
|
|
</div>
|
|
|
|
{/* Hero Content */}
|
|
<div className="relative z-10 text-center max-w-4xl px-6">
|
|
<div className="hero-stagger flex items-center justify-center gap-2 mb-4">
|
|
<span className="h-px w-8 bg-gray-400 dark:bg-gray-500"></span>
|
|
<span className="text-xs uppercase tracking-[0.2em] text-gray-600 dark:text-gray-400 font-medium">
|
|
Professional IT Services
|
|
</span>
|
|
<span className="h-px w-8 bg-gray-400 dark:bg-gray-500"></span>
|
|
</div>
|
|
|
|
<h1 className="hero-stagger font-display text-4xl md:text-5xl lg:text-6xl font-medium tracking-tighter leading-[1.1] mb-5 text-gray-900 dark:text-white">
|
|
{data.h1.split(' ').slice(0, -2).join(' ')}<br />
|
|
<span className="text-gray-500 dark:text-gray-500">
|
|
{data.h1.split(' ').slice(-2).join(' ')}
|
|
</span>
|
|
</h1>
|
|
|
|
<p className="hero-stagger text-base md:text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto mb-6 font-light leading-relaxed">
|
|
{data.description}
|
|
</p>
|
|
|
|
<div className="hero-stagger flex flex-col sm:flex-row items-center justify-center gap-4">
|
|
<motion.a
|
|
href="/contact"
|
|
className="selection-inverse px-8 py-3 bg-white dark:bg-white text-black dark:text-black rounded-full font-medium shadow-xl"
|
|
whileHover={{ scale: 1.05, backgroundColor: "#ffffff", color: "#000000" }}
|
|
whileTap={{ scale: 0.95 }}
|
|
>
|
|
Get Started
|
|
</motion.a>
|
|
<motion.a
|
|
href="/services"
|
|
className="px-8 py-3 bg-white/10 dark:bg-white/10 backdrop-blur-sm border-2 border-white/40 dark:border-white/40 text-white dark:text-white rounded-full font-medium shadow-xl"
|
|
whileHover={{ scale: 1.05, backgroundColor: "rgba(255,255,255,0.2)", borderColor: "#ffffff" }}
|
|
whileTap={{ scale: 0.95 }}
|
|
>
|
|
View All Services
|
|
</motion.a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Main Content Section */}
|
|
<section className="relative px-6 pt-8 pb-16 md:pt-10">
|
|
<div className="max-w-4xl mx-auto">
|
|
<div className="mb-4">
|
|
<Breadcrumb items={[
|
|
{ label: 'Home', to: '/' },
|
|
{ label: 'Services', to: '/services' },
|
|
{ label: data.h1 },
|
|
]} />
|
|
</div>
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.6 }}
|
|
className="bg-white/80 dark:bg-white/5 backdrop-blur-xl rounded-3xl p-8 md:p-10 lg:p-12 shadow-2xl border border-gray-100 dark:border-white/10"
|
|
>
|
|
<div className="prose prose-lg md:prose-xl dark:prose-invert max-w-none prose-headings:font-display prose-h2:text-3xl prose-h2:mb-6 prose-h2:mt-12 prose-h3:text-2xl prose-p:leading-relaxed prose-li:leading-relaxed prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-a:no-underline hover:prose-a:underline">
|
|
<div dangerouslySetInnerHTML={{ __html: data.content }} />
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Feature Highlight Section */}
|
|
<section className="px-6 py-16">
|
|
<div className="max-w-6xl mx-auto grid md:grid-cols-3 gap-8">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.1 }}
|
|
className="p-8 bg-white dark:bg-white/5 rounded-2xl border border-gray-100 dark:border-white/10 hover:shadow-xl transition-shadow"
|
|
>
|
|
<span className="material-symbols-outlined text-blue-600 dark:text-blue-400 text-5xl mb-4 block">
|
|
speed
|
|
</span>
|
|
<h3 className="font-display text-xl font-bold mb-3 text-gray-900 dark:text-white">
|
|
Fast Response
|
|
</h3>
|
|
<p className="text-gray-600 dark:text-gray-300">
|
|
Quick resolution of IT issues to minimize downtime and keep your business running smoothly.
|
|
</p>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.2 }}
|
|
className="p-8 bg-white dark:bg-white/5 rounded-2xl border border-gray-100 dark:border-white/10 hover:shadow-xl transition-shadow"
|
|
>
|
|
<span className="material-symbols-outlined text-blue-600 dark:text-blue-400 text-5xl mb-4 block">
|
|
verified_user
|
|
</span>
|
|
<h3 className="font-display text-xl font-bold mb-3 text-gray-900 dark:text-white">
|
|
Proactive Security
|
|
</h3>
|
|
<p className="text-gray-600 dark:text-gray-300">
|
|
Advanced security measures and monitoring to protect your business from cyber threats.
|
|
</p>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.3 }}
|
|
className="p-8 bg-white dark:bg-white/5 rounded-2xl border border-gray-100 dark:border-white/10 hover:shadow-xl transition-shadow"
|
|
>
|
|
<span className="material-symbols-outlined text-blue-600 dark:text-blue-400 text-5xl mb-4 block">
|
|
support_agent
|
|
</span>
|
|
<h3 className="font-display text-xl font-bold mb-3 text-gray-900 dark:text-white">
|
|
Expert Team
|
|
</h3>
|
|
<p className="text-gray-600 dark:text-gray-300">
|
|
Experienced IT professionals dedicated to providing exceptional service and support.
|
|
</p>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Services Section */}
|
|
<Services featuredIds={data.relatedServices} />
|
|
|
|
{/* Areas We Serve & CTA */}
|
|
<AreasWeServe />
|
|
<FAQ items={data.faq} />
|
|
<CTA />
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ServicePage;
|