Complete SEO overhaul

This commit is contained in:
Timo Knuth 2026-01-22 15:17:20 +01:00
parent e5c5503e08
commit 42e0971a13
37 changed files with 4319 additions and 1217 deletions

31
App.tsx
View File

@ -12,6 +12,10 @@ import AboutPage from './src/pages/AboutPage';
import ServicesPage from './src/pages/ServicesPage'; import ServicesPage from './src/pages/ServicesPage';
import BlogPage from './src/pages/BlogPage'; import BlogPage from './src/pages/BlogPage';
import ContactPage from './src/pages/ContactPage'; import ContactPage from './src/pages/ContactPage';
import LocationPage from './src/pages/LocationPage';
import ServicePage from './src/pages/ServicePage';
import BlogPostPage from './src/pages/BlogPostPage';
import { locationData, serviceData, blogPostData } from './src/data/seoData';
// Register GSAP plugins globally // Register GSAP plugins globally
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin); gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);
@ -69,6 +73,33 @@ const AppContent: React.FC = () => {
<Route path="/services" element={<ServicesPage />} /> <Route path="/services" element={<ServicesPage />} />
<Route path="/blog" element={<BlogPage />} /> <Route path="/blog" element={<BlogPage />} />
<Route path="/contact" element={<ContactPage />} /> <Route path="/contact" element={<ContactPage />} />
{/* SEO Location Pages */}
{locationData.map((data) => (
<Route
key={data.slug}
path={`/${data.slug}`}
element={<LocationPage data={data} />}
/>
))}
{/* SEO Service Pages */}
{serviceData.map((data) => (
<Route
key={data.slug}
path={`/${data.slug}`}
element={<ServicePage data={data} />}
/>
))}
{/* Authority Blog Posts */}
{blogPostData.map((data) => (
<Route
key={data.slug}
path={`/${data.slug}`}
element={<BlogPostPage data={data} />}
/>
))}
</Routes> </Routes>
</main> </main>
<Footer /> <Footer />

View File

@ -0,0 +1,43 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { locationData } from '../src/data/seoData';
const AreasWeServe: React.FC = () => {
return (
<section className="py-24 px-6 bg-gray-50 dark:bg-white/5 mx-auto text-center border-t border-gray-200 dark:border-white/10">
<div className="max-w-4xl mx-auto">
<h2 className="font-display text-3xl md:text-4xl font-bold mb-6 text-gray-900 dark:text-white">
Areas We Serve Local IT Support Across the Coastal Bend
</h2>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-12 leading-relaxed">
We provide professional IT support and IT services for businesses throughout Corpus Christi and the surrounding Coastal Bend area.
Our team supports local companies with business IT support, outsourced IT services, and help desk solutions, delivered remotely or on-site when needed.
</p>
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-12">
{locationData.map((loc) => (
<Link
key={loc.slug}
to={`/${loc.slug}`}
className="p-4 bg-white dark:bg-white/10 rounded-lg shadow-sm hover:shadow-md transition-all text-gray-800 dark:text-gray-200 font-medium hover:text-black dark:hover:text-white"
>
{loc.city}
</Link>
))}
</div>
<p className="text-lg text-gray-600 dark:text-gray-400">
Not sure if your location is covered? <Link to="/contact" className="underline hover:text-black dark:hover:text-white transition-colors">Contact us today</Link> to discuss your IT needs.
</p>
<div className="mt-6">
<Link to="/it-support-corpus-christi" className="text-sm text-gray-500 hover:text-gray-800 dark:hover:text-gray-300 transition-colors">
Get local IT support in Corpus Christi and nearby areas
</Link>
</div>
</div>
</section>
);
};
export default AreasWeServe;

View File

@ -1,74 +1,74 @@
import React from 'react'; import React from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
const CTA: React.FC = () => { const CTA: React.FC = () => {
return ( return (
<section className="py-24 px-6 bg-white dark:bg-[#0f0f0f] border-t border-gray-100 dark:border-white/5 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]"> <section className="py-24 px-6 bg-white dark:bg-[#0f0f0f] border-t border-gray-100 dark:border-white/5 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
<div className="max-w-4xl mx-auto text-center"> <div className="max-w-4xl mx-auto text-center">
<motion.h2 <motion.h2
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
className="font-display text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white" className="font-display text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white"
> >
Ready for <span className="text-gray-400 dark:text-gray-500">reliable IT?</span> Ready for <span className="text-gray-400 dark:text-gray-500">reliable IT?</span>
</motion.h2> </motion.h2>
<motion.p <motion.p
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay: 0.1 }} transition={{ delay: 0.1 }}
className="text-xl text-gray-600 dark:text-gray-300 mb-12 leading-relaxed" className="text-xl text-gray-600 dark:text-gray-300 mb-12 leading-relaxed"
> >
Join 150+ Coastal Bend businesses that trust us with their technology. Get started with a free 20-minute assessment. Join 150+ Coastal Bend businesses that trust us with their technology. Get started with a free 20-minute assessment.
</motion.p> </motion.p>
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
className="flex flex-col sm:flex-row gap-4 justify-center items-center" className="flex flex-col sm:flex-row gap-4 justify-center items-center"
> >
<Link <Link
to="/contact" to="/contact"
className="px-8 py-4 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium transition-all hover:scale-105 shadow-lg w-full sm:w-auto" className="px-8 py-4 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium transition-all hover:scale-105 shadow-lg w-full sm:w-auto"
> >
Book a 20-minute assessment Book a 20-minute assessment
</Link> </Link>
<Link <Link
to="/contact" to="/contact"
className="px-8 py-4 bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-white rounded-full font-medium transition-all hover:bg-gray-200 dark:hover:bg-white/20 w-full sm:w-auto" className="px-8 py-4 bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-white rounded-full font-medium transition-all hover:bg-gray-200 dark:hover:bg-white/20 w-full sm:w-auto"
> >
Send a message Send a message
</Link> </Link>
</motion.div> </motion.div>
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }} whileInView={{ opacity: 1 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay: 0.3 }} transition={{ delay: 0.3 }}
className="mt-16 grid md:grid-cols-3 gap-8 text-left" className="mt-16 grid md:grid-cols-3 gap-8 text-left"
> >
<div className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10"> <div className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10">
<h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">How quickly can you start?</h3> <h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">How quickly can you start?</h3>
<p className="text-gray-600 dark:text-gray-400 text-sm">Most assessments can begin within 48 hours of contact.</p> <p className="text-gray-600 dark:text-gray-400 text-sm">Most assessments can begin within 48 hours of contact.</p>
</div> </div>
<div className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10"> <div className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10">
<h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">How do you price services?</h3> <h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">How do you price services?</h3>
<p className="text-gray-600 dark:text-gray-400 text-sm">Transparent monthly pricing based on devices and services needed.</p> <p className="text-gray-600 dark:text-gray-400 text-sm">Transparent monthly pricing based on devices and services needed.</p>
</div> </div>
<div className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10"> <div className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10">
<h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">What's included in support?</h3> <h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">What's included in support?</h3>
<p className="text-gray-600 dark:text-gray-400 text-sm">24/7 monitoring, helpdesk, proactive maintenance, and SLA guarantees.</p> <p className="text-gray-600 dark:text-gray-400 text-sm">24/7 monitoring, helpdesk, proactive maintenance, and SLA guarantees.</p>
</div> </div>
</motion.div> </motion.div>
</div> </div>
</section> </section>
); );
}; };
export default CTA; export default CTA;

View File

@ -1,28 +1,28 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { motion, useInView, useSpring, useTransform } from 'framer-motion'; import { motion, useInView, useSpring, useTransform } from 'framer-motion';
interface CounterProps { interface CounterProps {
value: number; value: number;
} }
const Counter: React.FC<CounterProps> = ({ value }) => { const Counter: React.FC<CounterProps> = ({ value }) => {
const ref = useRef(null); const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: "-20%" }); const isInView = useInView(ref, { once: true, margin: "-20%" });
// Using slow/heavy physics as requested for premium feel // Using slow/heavy physics as requested for premium feel
const spring = useSpring(0, { mass: 3, stiffness: 75, damping: 30 }); const spring = useSpring(0, { mass: 3, stiffness: 75, damping: 30 });
const display = useTransform(spring, (current) => const display = useTransform(spring, (current) =>
// formatting: if decimal exists in target, show 1 decimal, else integer // formatting: if decimal exists in target, show 1 decimal, else integer
value % 1 !== 0 ? current.toFixed(1) : Math.round(current).toLocaleString() value % 1 !== 0 ? current.toFixed(1) : Math.round(current).toLocaleString()
); );
useEffect(() => { useEffect(() => {
if (isInView) { if (isInView) {
spring.set(value); spring.set(value);
} }
}, [isInView, value, spring]); }, [isInView, value, spring]);
return <motion.span ref={ref}>{display}</motion.span>; return <motion.span ref={ref}>{display}</motion.span>;
}; };
export default Counter; export default Counter;

65
components/FAQ.tsx Normal file
View File

@ -0,0 +1,65 @@
import React from 'react';
import { motion } from 'framer-motion';
import { FAQItem } from '../src/data/seoData';
interface FAQProps {
items: FAQItem[];
}
const FAQ: React.FC<FAQProps> = ({ items }) => {
if (!items || items.length === 0) return null;
return (
<section className="py-24 px-6 bg-white dark:bg-black">
<div className="max-w-3xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<h2 className="font-display text-3xl md:text-4xl font-bold mb-4 text-gray-900 dark:text-white">
Frequently Asked Questions
</h2>
<p className="text-gray-600 dark:text-gray-400">
Common questions about our IT services.
</p>
</motion.div>
<div className="space-y-6">
{items.map((faq, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10"
>
<h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">{faq.question}</h3>
<p className="text-gray-600 dark:text-gray-300">{faq.answer}</p>
</motion.div>
))}
</div>
</div>
{/* JSON-LD for FAQ Page */}
<script type="application/ld+json" dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": items.map(faq => ({
"@type": "Question",
"name": faq.question,
"acceptedAnswer": {
"@type": "Answer",
"text": faq.answer
}
}))
})
}} />
</section>
);
};
export default FAQ;

68
components/SEO.tsx Normal file
View File

@ -0,0 +1,68 @@
import React, { useEffect } from 'react';
interface SEOProps {
title: string;
description: string;
keywords?: string[];
canonicalUrl?: string;
schema?: object; // JSON-LD schema
}
const SEO: React.FC<SEOProps> = ({ title, description, keywords, canonicalUrl, schema }) => {
useEffect(() => {
// Update Title
document.title = title;
// Helper to set meta tag
const setMetaTag = (name: string, content: string) => {
let element = document.querySelector(`meta[name="${name}"]`);
if (!element) {
element = document.createElement('meta');
element.setAttribute('name', name);
document.head.appendChild(element);
}
element.setAttribute('content', content);
};
// Update Meta Description
setMetaTag('description', description);
// Update Keywords
if (keywords && keywords.length > 0) {
setMetaTag('keywords', keywords.join(', '));
}
// Update Canonical
if (canonicalUrl) {
let link = document.querySelector('link[rel="canonical"]');
if (!link) {
link = document.createElement('link');
link.setAttribute('rel', 'canonical');
document.head.appendChild(link);
}
link.setAttribute('href', canonicalUrl);
}
// Inject Schema
if (schema) {
const scriptId = 'seo-schema-script';
let script = document.getElementById(scriptId);
if (!script) {
script = document.createElement('script');
script.id = scriptId;
script.setAttribute('type', 'application/ld+json');
document.head.appendChild(script);
}
script.textContent = JSON.stringify(schema);
}
// Cleanup function not strictly necessary for single page app navigation
// unless we want to remove specific tags on unmount, but usually we just overwrite them.
}, [title, description, keywords, canonicalUrl, schema]);
return null;
};
export default SEO;

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, useLayoutEffect } from 'react'; import React, { useState, useRef, useLayoutEffect, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import gsap from 'gsap'; import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger'; import { ScrollTrigger } from 'gsap/ScrollTrigger';
@ -69,45 +69,68 @@ const servicesData = [
description: 'Selection, setup, and maintenance of Network Attached Storage solutions to provide scalable and reliable data storage.', description: 'Selection, setup, and maintenance of Network Attached Storage solutions to provide scalable and reliable data storage.',
icon: 'storage', icon: 'storage',
image: '/assets/services/nas-storage.png' image: '/assets/services/nas-storage.png'
},
{
id: 9,
category: 'IT Infrastructure',
title: 'Business IT Support',
description: 'Comprehensive IT support for businesses, including help desk, maintenance, and strategic planning.',
icon: 'business_center',
image: '/assets/services/business-it.png'
},
{
id: 10,
category: 'IT Infrastructure',
title: 'IT Help Desk',
description: 'Fast and reliable help desk support for employees, resolving technical issues remotely or on-site.',
icon: 'support_agent',
image: '/assets/services/help-desk.png'
},
{
id: 11,
category: 'IT Infrastructure',
title: 'Managed IT Services',
description: 'Proactive monitoring, security, and management of your entire IT infrastructure for a fixed monthly fee.',
icon: 'admin_panel_settings',
image: '/assets/services/managed-it.png'
} }
]; ];
const categories = ['All', 'IT Infrastructure', 'Web Services', 'Security', 'Networking']; const categories = ['All', 'IT Infrastructure', 'Web Services', 'Security', 'Networking'];
const Services: React.FC<{ preview?: boolean }> = ({ preview = false }) => { interface ServicesProps {
preview?: boolean;
featuredIds?: number[];
}
const Services: React.FC<ServicesProps> = ({ preview = false, featuredIds }) => {
const [activeCategory, setActiveCategory] = useState('All'); const [activeCategory, setActiveCategory] = useState('All');
const [showAll, setShowAll] = useState(false);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const imagesRef = useRef<(HTMLDivElement | null)[]>([]);
// Reset refs on render to handle filtering updates // Determine if we should be in "preview mode" (showing only a subset)
imagesRef.current = []; // This applies if preview is true OR if featuredIds are provided and we haven't clicked "Show More"
const isRestrictedView = (preview || featuredIds) && !showAll;
const filteredServices = activeCategory === 'All' // Filter services based on category first (unless in restricted view with specific IDs, where we might want to ignore category or just show the specific ones)
const filteredByCategory = activeCategory === 'All'
? servicesData ? servicesData
: servicesData.filter(s => s.category === activeCategory || (activeCategory === 'Web Development' && s.category === 'Security')); : servicesData.filter(s => s.category === activeCategory || (activeCategory === 'Web Development' && s.category === 'Security'));
const displayedServices = preview ? servicesData.slice(0, 3) : filteredServices; const displayedServices = useMemo(() => {
if (isRestrictedView) {
useLayoutEffect(() => { if (featuredIds && featuredIds.length > 0) {
const ctx = gsap.context(() => { // Sort the services to match the order of featuredIds
imagesRef.current.forEach((imgWrapper) => { return featuredIds
if (!imgWrapper) return; .map(id => servicesData.find(s => s.id === id))
.filter((s): s is typeof servicesData[0] => s !== undefined);
gsap.to(imgWrapper, { }
yPercent: 30, // Fallback to first 3 if no IDs but preview is true
ease: "none", return servicesData.slice(0, 3);
scrollTrigger: { }
trigger: imgWrapper.closest('.group'), // Show all (filtered by category)
start: "top bottom", return filteredByCategory;
end: "bottom top", }, [isRestrictedView, featuredIds, filteredByCategory]);
scrub: true
}
});
});
}, containerRef);
return () => ctx.revert();
}, [filteredServices]);
return ( return (
<motion.section <motion.section
@ -127,32 +150,35 @@ const Services: React.FC<{ preview?: boolean }> = ({ preview = false }) => {
</h2> </h2>
</div> </div>
<div className="flex gap-6 mb-12 border-b border-gray-200 dark:border-white/10 text-sm font-medium overflow-x-auto pb-2 no-scrollbar"> {/* Categories - Hide in restricted view to keep it clean, or keep it? User said "mach nur das 3 services angezeigt werden". usually categories are for the full list. */}
{categories.map((cat) => ( {!isRestrictedView && (
<button <div className="flex gap-6 mb-12 border-b border-gray-200 dark:border-white/10 text-sm font-medium overflow-x-auto pb-2 no-scrollbar">
key={cat} {categories.map((cat) => (
onClick={() => setActiveCategory(cat)} <button
className={`pb-2 whitespace-nowrap transition-colors relative ${activeCategory === cat key={cat}
? 'text-gray-900 dark:text-white' onClick={() => setActiveCategory(cat)}
: 'text-gray-500 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-300' className={`pb-2 whitespace-nowrap transition-colors relative ${activeCategory === cat
}`} ? 'text-gray-900 dark:text-white'
> : 'text-gray-500 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-300'
{cat} }`}
{activeCategory === cat && ( >
<motion.div {cat}
layoutId="activeTab" {activeCategory === cat && (
className="absolute bottom-0 left-0 right-0 h-0.5 bg-black dark:bg-white" <motion.div
/> layoutId="activeTab"
)} className="absolute bottom-0 left-0 right-0 h-0.5 bg-black dark:bg-white"
</button> />
))} )}
</div> </button>
))}
</div>
)}
<div <div
className="grid grid-cols-1 md:grid-cols-3 gap-6" className="grid grid-cols-1 md:grid-cols-3 gap-6"
> >
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{filteredServices.map((service, index) => ( {displayedServices.map((service) => (
<motion.div <motion.div
key={service.id} key={service.id}
layout layout
@ -164,34 +190,28 @@ const Services: React.FC<{ preview?: boolean }> = ({ preview = false }) => {
className="group relative bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl overflow-hidden hover:border-gray-300 dark:hover:border-white/30 hover:shadow-2xl transition-all duration-300" className="group relative bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl overflow-hidden hover:border-gray-300 dark:hover:border-white/30 hover:shadow-2xl transition-all duration-300"
> >
{/* Image Container */} {/* Image Container */}
<div className="h-64 bg-gray-200 dark:bg-black/40 overflow-hidden relative"> <div className="h-40 bg-gray-200 dark:bg-black/40 overflow-hidden relative">
{/* Parallax Wrapper */} <img
<div src={service.image}
ref={el => { if (el) imagesRef.current.push(el); }} alt={service.title}
className="w-full h-[140%] -mt-[20%]" className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110 opacity-100"
> />
<img
src={service.image}
alt={service.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110 opacity-100"
/>
</div>
<div className="absolute inset-0 bg-gradient-to-t from-gray-50 dark:from-[#161616] to-transparent pointer-events-none"></div> <div className="absolute inset-0 bg-gradient-to-t from-gray-50 dark:from-[#161616] to-transparent pointer-events-none"></div>
</div> </div>
<div className="p-6 relative"> <div className="p-4 relative">
<motion.div <motion.div
className="w-10 h-10 rounded-full bg-white dark:bg-white/10 flex items-center justify-center mb-4 border border-gray-200 dark:border-white/10" className="w-8 h-8 rounded-full bg-white dark:bg-white/10 flex items-center justify-center mb-3 border border-gray-200 dark:border-white/10"
whileHover={{ rotate: 360, backgroundColor: "#171717", color: "#ffffff", borderColor: "#171717" }} whileHover={{ rotate: 360, backgroundColor: "#171717", color: "#ffffff", borderColor: "#171717" }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
> >
<span className="material-symbols-outlined text-sm text-gray-900 dark:text-white group-hover:text-white">{service.icon}</span> <span className="material-symbols-outlined text-sm text-gray-900 dark:text-white group-hover:text-white">{service.icon}</span>
</motion.div> </motion.div>
<h3 className="font-display text-xl font-bold text-gray-900 dark:text-white mb-2">{service.title}</h3> <h3 className="font-display text-lg font-bold text-gray-900 dark:text-white mb-2">{service.title}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed mb-4"> <p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed mb-3">
{service.description} {service.description}
</p> </p>
<a href="#" className="inline-flex items-center text-xs font-bold uppercase tracking-wide text-gray-900 dark:text-white group-hover:text-gray-600 dark:group-hover:text-gray-300 transition-colors"> <a href="/services" className="inline-flex items-center text-xs font-bold uppercase tracking-wide text-gray-900 dark:text-white group-hover:text-gray-600 dark:group-hover:text-gray-300 transition-colors">
Learn More <motion.span Learn More <motion.span
className="material-symbols-outlined text-xs ml-1" className="material-symbols-outlined text-xs ml-1"
animate={{ x: [0, 5, 0] }} animate={{ x: [0, 5, 0] }}
@ -204,16 +224,18 @@ const Services: React.FC<{ preview?: boolean }> = ({ preview = false }) => {
</AnimatePresence> </AnimatePresence>
</div> </div>
{preview && ( {isRestrictedView && (
<div className="mt-12 text-center"> <div className="mt-12 text-center">
<a <button
href="/services" onClick={() => setShowAll(true)}
className="inline-flex items-center gap-2 px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors" className="inline-flex items-center gap-2 px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors"
> >
View all services <span className="material-symbols-outlined text-sm">arrow_forward</span> Show More Services <span className="material-symbols-outlined text-sm">expand_more</span>
</a> </button>
</div> </div>
)} )}
{/* If we are showing all and originally had a restricted view, maybe show a "Show Less" but user didn't ask for it. The user said "then all are shown". */}
</div> </div>
</motion.section> </motion.section>
); );

View File

@ -1,44 +1,44 @@
import React from 'react'; import React from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
const Testimonials: React.FC = () => { const Testimonials: React.FC = () => {
return ( return (
<section className="py-24 px-6 bg-background-light dark:bg-background-dark relative overflow-hidden bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]"> <section className="py-24 px-6 bg-background-light dark:bg-background-dark relative overflow-hidden bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<motion.div <motion.div
initial={{ opacity: 0, scale: 0.95 }} initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }} whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }} viewport={{ once: true }}
className="bg-white dark:bg-white/5 backdrop-blur-sm p-8 md:p-12 rounded-3xl border border-gray-200 dark:border-white/10 shadow-2xl relative" className="bg-white dark:bg-white/5 backdrop-blur-sm p-8 md:p-12 rounded-3xl border border-gray-200 dark:border-white/10 shadow-2xl relative"
> >
{/* Quote Icon */} {/* Quote Icon */}
<div className="absolute top-8 right-8 text-blue-100 dark:text-white/5 select-none"> <div className="absolute top-8 right-8 text-blue-100 dark:text-white/5 select-none">
<span className="material-symbols-outlined text-8xl">format_quote</span> <span className="material-symbols-outlined text-8xl">format_quote</span>
</div> </div>
<div className="flex text-yellow-400 mb-6 gap-1 relative z-10"> <div className="flex text-yellow-400 mb-6 gap-1 relative z-10">
{[1, 2, 3, 4, 5].map((star) => ( {[1, 2, 3, 4, 5].map((star) => (
<span key={star} className="material-symbols-outlined fill-current">star</span> <span key={star} className="material-symbols-outlined fill-current">star</span>
))} ))}
</div> </div>
<blockquote className="text-xl md:text-2xl font-medium leading-relaxed text-gray-900 dark:text-white mb-8 relative z-10"> <blockquote className="text-xl md:text-2xl font-medium leading-relaxed text-gray-900 dark:text-white mb-8 relative z-10">
"Bay Area Affiliates transformed our IT infrastructure completely. Their proactive approach means we rarely have downtime, and when issues do arise, they're resolved quickly. Our team can focus on patient care instead of tech problems." "Bay Area Affiliates transformed our IT infrastructure completely. Their proactive approach means we rarely have downtime, and when issues do arise, they're resolved quickly. Our team can focus on patient care instead of tech problems."
</blockquote> </blockquote>
<div className="flex items-center gap-4 relative z-10"> <div className="flex items-center gap-4 relative z-10">
<div className="w-12 h-12 bg-black dark:bg-white rounded-full flex items-center justify-center text-white dark:text-black font-bold text-lg"> <div className="w-12 h-12 bg-black dark:bg-white rounded-full flex items-center justify-center text-white dark:text-black font-bold text-lg">
SM SM
</div> </div>
<div> <div>
<div className="font-bold text-gray-900 dark:text-white">Sarah Martinez</div> <div className="font-bold text-gray-900 dark:text-white">Sarah Martinez</div>
<div className="text-sm text-gray-500 dark:text-gray-400">Operations Manager, Coastal Medical Group</div> <div className="text-sm text-gray-500 dark:text-gray-400">Operations Manager, Coastal Medical Group</div>
</div> </div>
</div> </div>
</motion.div> </motion.div>
</div> </div>
</section> </section>
); );
}; };
export default Testimonials; export default Testimonials;

View File

@ -65,6 +65,22 @@
.lenis.lenis-scrolling iframe { .lenis.lenis-scrolling iframe {
pointer-events: none; pointer-events: none;
} }
/* Custom Scrollbar */
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
</style> </style>
<body class="bg-background-light dark:bg-background-dark text-gray-900 dark:text-white font-sans antialiased selection:bg-white selection:text-black transition-colors duration-300"> <body class="bg-background-light dark:bg-background-dark text-gray-900 dark:text-white font-sans antialiased selection:bg-white selection:text-black transition-colors duration-300">
<div id="root"></div> <div id="root"></div>

528
package-lock.json generated
View File

@ -18,6 +18,7 @@
"devDependencies": { "devDependencies": {
"@types/node": "^22.14.0", "@types/node": "^22.14.0",
"@vitejs/plugin-react": "^5.0.0", "@vitejs/plugin-react": "^5.0.0",
"tsx": "^4.21.0",
"typescript": "~5.8.2", "typescript": "~5.8.2",
"vite": "^6.2.0" "vite": "^6.2.0"
} }
@ -1475,6 +1476,19 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/get-tsconfig": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/gsap": { "node_modules/gsap": {
"version": "3.14.2", "version": "3.14.2",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz", "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz",
@ -1690,6 +1704,16 @@
"react-dom": ">=18" "react-dom": ">=18"
} }
}, },
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.55.1", "version": "4.55.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
@ -1790,6 +1814,510 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tsx": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/esbuild": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.2",
"@esbuild/android-arm": "0.27.2",
"@esbuild/android-arm64": "0.27.2",
"@esbuild/android-x64": "0.27.2",
"@esbuild/darwin-arm64": "0.27.2",
"@esbuild/darwin-x64": "0.27.2",
"@esbuild/freebsd-arm64": "0.27.2",
"@esbuild/freebsd-x64": "0.27.2",
"@esbuild/linux-arm": "0.27.2",
"@esbuild/linux-arm64": "0.27.2",
"@esbuild/linux-ia32": "0.27.2",
"@esbuild/linux-loong64": "0.27.2",
"@esbuild/linux-mips64el": "0.27.2",
"@esbuild/linux-ppc64": "0.27.2",
"@esbuild/linux-riscv64": "0.27.2",
"@esbuild/linux-s390x": "0.27.2",
"@esbuild/linux-x64": "0.27.2",
"@esbuild/netbsd-arm64": "0.27.2",
"@esbuild/netbsd-x64": "0.27.2",
"@esbuild/openbsd-arm64": "0.27.2",
"@esbuild/openbsd-x64": "0.27.2",
"@esbuild/openharmony-arm64": "0.27.2",
"@esbuild/sunos-x64": "0.27.2",
"@esbuild/win32-arm64": "0.27.2",
"@esbuild/win32-ia32": "0.27.2",
"@esbuild/win32-x64": "0.27.2"
}
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.8.3", "version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",

View File

@ -6,7 +6,8 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview",
"generate:seo": "npx tsx scripts/generate-sitemap.ts && npx tsx scripts/generate-robots.ts"
}, },
"dependencies": { "dependencies": {
"@studio-freight/lenis": "^1.0.42", "@studio-freight/lenis": "^1.0.42",
@ -19,6 +20,7 @@
"devDependencies": { "devDependencies": {
"@types/node": "^22.14.0", "@types/node": "^22.14.0",
"@vitejs/plugin-react": "^5.0.0", "@vitejs/plugin-react": "^5.0.0",
"tsx": "^4.21.0",
"typescript": "~5.8.2", "typescript": "~5.8.2",
"vite": "^6.2.0" "vite": "^6.2.0"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 KiB

6
public/robots.txt Normal file
View File

@ -0,0 +1,6 @@
User-agent: *
Allow: /
Disallow: /admin
Disallow: /api
Sitemap: https://bayareait.services/sitemap.xml

147
public/sitemap.xml Normal file
View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://bayareait.services</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://bayareait.services/services</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://bayareait.services/blog</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://bayareait.services/contact</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://bayareait.services/about</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://bayareait.services/it-support-corpus-christi</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://bayareait.services/it-support-portland-tx</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://bayareait.services/it-support-rockport-tx</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://bayareait.services/it-support-aransas-pass-tx</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://bayareait.services/it-support-kingsville-tx</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://bayareait.services/business-it-support</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://bayareait.services/it-help-desk</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://bayareait.services/computer-support</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://bayareait.services/managed-it-services-corpus-christi</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://bayareait.services/blog/it-support-small-business-corpus-christi</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://bayareait.services/blog/outsourced-it-support-corpus-christi</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://bayareait.services/blog/it-service-vs-inhouse-it</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://bayareait.services/blog/common-it-problems-businesses-corpus-christi</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://bayareait.services/blog/it-support-cost-corpus-christi</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://bayareait.services/blog/it-support-corpus-christi-blog</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://bayareait.services/blog/it-support-portland-tx-blog</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://bayareait.services/blog/it-support-rockport-tx-blog</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://bayareait.services/blog/it-support-aransas-pass-blog</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://bayareait.services/blog/it-support-kingsville-tx-blog</loc>
<lastmod>2026-01-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
</urlset>

View File

@ -0,0 +1,28 @@
import fs from 'fs';
import path from 'path';
const BASE_URL = process.env.BASE_URL || 'https://bayareait.services';
const generateRobots = () => {
const content = `User-agent: *
Allow: /
Disallow: /admin
Disallow: /api
Sitemap: ${BASE_URL}/sitemap.xml
`;
return content;
};
const robots = generateRobots();
const outputPath = path.resolve(process.cwd(), 'public/robots.txt');
// Ensure public directory exists
const publicDir = path.dirname(outputPath);
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
fs.writeFileSync(outputPath, robots);
console.log(`✅ Robots.txt generated at ${outputPath}`);

View File

@ -0,0 +1,85 @@
import fs from 'fs';
import path from 'path';
import { locationData, serviceData, blogPostData } from '../src/data/seoData';
const BASE_URL = process.env.BASE_URL || 'https://bayareait.services';
/**
* Generates the sitemap.xml content
*/
const generateSitemap = () => {
const currentDate = new Date().toISOString().split('T')[0];
let xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
`;
// Static Pages
const staticPages = [
'',
'/services',
'/blog',
'/contact',
'/about'
];
staticPages.forEach(page => {
xml += ` <url>
<loc>${BASE_URL}${page}</loc>
<lastmod>${currentDate}</lastmod>
<changefreq>monthly</changefreq>
<priority>${page === '' ? '1.0' : '0.8'}</priority>
</url>
`;
});
// Location Pages
locationData.forEach(page => {
xml += ` <url>
<loc>${BASE_URL}/${page.slug}</loc>
<lastmod>${currentDate}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
`;
});
// Service Pages
serviceData.forEach(page => {
xml += ` <url>
<loc>${BASE_URL}/${page.slug}</loc>
<lastmod>${currentDate}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
`;
});
// Blog Posts
blogPostData.forEach(post => {
xml += ` <url>
<loc>${BASE_URL}/blog/${post.slug}</loc>
<lastmod>${currentDate}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
`;
});
xml += `</urlset>`;
return xml;
};
// Write to public/sitemap.xml
const sitemap = generateSitemap();
const outputPath = path.resolve(process.cwd(), 'public/sitemap.xml');
// Ensure public directory exists
const publicDir = path.dirname(outputPath);
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
fs.writeFileSync(outputPath, sitemap);
console.log(`✅ Sitemap generated at ${outputPath}`);

1251
src/data/seoData.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,246 +1,246 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { motion, useInView, useSpring, useTransform, useScroll, useMotionValueEvent } from 'framer-motion'; import { motion, useInView, useSpring, useTransform, useScroll, useMotionValueEvent } from 'framer-motion';
import Contact from '../../components/Contact'; import Contact from '../../components/Contact';
const Counter = ({ value }: { value: number }) => { const Counter = ({ value }: { value: number }) => {
const ref = useRef(null); const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: "-20%" }); const isInView = useInView(ref, { once: true, margin: "-20%" });
const spring = useSpring(0, { mass: 3, stiffness: 75, damping: 30 }); const spring = useSpring(0, { mass: 3, stiffness: 75, damping: 30 });
const display = useTransform(spring, (current) => const display = useTransform(spring, (current) =>
value % 1 !== 0 ? current.toFixed(1) : Math.round(current).toLocaleString() value % 1 !== 0 ? current.toFixed(1) : Math.round(current).toLocaleString()
); );
useEffect(() => { useEffect(() => {
if (isInView) { if (isInView) {
spring.set(value); spring.set(value);
} }
}, [isInView, value, spring]); }, [isInView, value, spring]);
return <motion.span ref={ref}>{display}</motion.span>; return <motion.span ref={ref}>{display}</motion.span>;
}; };
const AboutPage: React.FC = () => { const AboutPage: React.FC = () => {
const timelineRef = useRef<HTMLDivElement>(null); const timelineRef = useRef<HTMLDivElement>(null);
const [activeTimelineIndex, setActiveTimelineIndex] = useState(0); const [activeTimelineIndex, setActiveTimelineIndex] = useState(0);
const { scrollYProgress } = useScroll({ const { scrollYProgress } = useScroll({
target: timelineRef, target: timelineRef,
offset: ["start end", "end center"] offset: ["start end", "end center"]
}); });
const heightTransform = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]); const heightTransform = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
useMotionValueEvent(scrollYProgress, "change", (latest) => { useMotionValueEvent(scrollYProgress, "change", (latest) => {
// Calculate index based on scroll progress with a slight offset to trigger earlier // Calculate index based on scroll progress with a slight offset to trigger earlier
const index = Math.floor(latest * 4.5); // 4 items, multiplier slightly > 4 ensures last one gets hit const index = Math.floor(latest * 4.5); // 4 items, multiplier slightly > 4 ensures last one gets hit
setActiveTimelineIndex(Math.min(index, 3)); setActiveTimelineIndex(Math.min(index, 3));
}); });
useEffect(() => { useEffect(() => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
}, []); }, []);
const stats = [ const stats = [
{ label: 'Businesses served', value: '150+' }, { label: 'Businesses served', value: '150+' },
{ label: 'Uptime achieved', value: '99.9%' }, { label: 'Uptime achieved', value: '99.9%' },
{ label: 'Years of service', value: '15+' }, { label: 'Years of service', value: '15+' },
{ label: 'Response time', value: '<2min' }, { label: 'Response time', value: '<2min' },
]; ];
const values = [ const values = [
{ {
title: 'Security-First', title: 'Security-First',
desc: 'Every solution we implement prioritizes your data security and business continuity.', desc: 'Every solution we implement prioritizes your data security and business continuity.',
icon: 'security' icon: 'security'
}, },
{ {
title: 'Reliability', title: 'Reliability',
desc: 'We build systems that work consistently, so you can depend on your technology.', desc: 'We build systems that work consistently, so you can depend on your technology.',
icon: 'verified' icon: 'verified'
}, },
{ {
title: 'Clarity', title: 'Clarity',
desc: 'No tech jargon or hidden fees. We explain what we do and why it matters.', desc: 'No tech jargon or hidden fees. We explain what we do and why it matters.',
icon: 'visibility' icon: 'visibility'
} }
]; ];
const timeline = [ const timeline = [
{ year: '2010', title: 'Founded in Corpus Christi', desc: 'Started with a mission to bring enterprise-level IT solutions to local businesses.' }, { year: '2010', title: 'Founded in Corpus Christi', desc: 'Started with a mission to bring enterprise-level IT solutions to local businesses.' },
{ year: '2015', title: 'Expanded Service Portfolio', desc: 'Added cloud services and advanced networking to serve growing businesses.' }, { year: '2015', title: 'Expanded Service Portfolio', desc: 'Added cloud services and advanced networking to serve growing businesses.' },
{ year: '2020', title: 'Remote Work Transformation', desc: 'Helped 100+ businesses transition to secure remote work during the pandemic.' }, { year: '2020', title: 'Remote Work Transformation', desc: 'Helped 100+ businesses transition to secure remote work during the pandemic.' },
{ year: '2024', title: 'Leading the Coastal Bend', desc: 'Now serving 150+ businesses with modern, reliable IT infrastructure.' }, { year: '2024', title: 'Leading the Coastal Bend', desc: 'Now serving 150+ businesses with modern, reliable IT infrastructure.' },
]; ];
return ( return (
<div className="pt-20 min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden"> <div className="pt-20 min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" /> <div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" />
{/* Hero Section */} {/* Hero Section */}
<section className="relative py-20 px-6 overflow-hidden"> <section className="relative py-20 px-6 overflow-hidden">
<div className="max-w-7xl mx-auto text-center relative z-10"> <div className="max-w-7xl mx-auto text-center relative z-10">
<motion.h1 <motion.h1
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
className="font-display text-4xl md:text-6xl font-bold mb-6 text-gray-900 dark:text-white" className="font-display text-4xl md:text-6xl font-bold mb-6 text-gray-900 dark:text-white"
> >
Local IT expertise for the <br /><span className="text-gray-500 dark:text-gray-400">Coastal Bend</span> Local IT expertise for the <br /><span className="text-gray-500 dark:text-gray-400">Coastal Bend</span>
</motion.h1> </motion.h1>
<motion.p <motion.p
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }} transition={{ delay: 0.1 }}
className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto leading-relaxed" className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto leading-relaxed"
> >
Since 2010, we've been helping businesses in Corpus Christi and surrounding communities build reliable, secure technology foundations that drive growth. Since 2010, we've been helping businesses in Corpus Christi and surrounding communities build reliable, secure technology foundations that drive growth.
</motion.p> </motion.p>
</div> </div>
</section> </section>
{/* Our Story */} {/* Our Story */}
<section className="py-20 px-6 relative bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]"> <section className="py-20 px-6 relative bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[100px] pointer-events-none" /> <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[100px] pointer-events-none" />
<div className="max-w-4xl mx-auto relative z-10"> <div className="max-w-4xl mx-auto relative z-10">
<h2 className="font-display text-3xl font-bold mb-8 text-gray-900 dark:text-white">Our Story</h2> <h2 className="font-display text-3xl font-bold mb-8 text-gray-900 dark:text-white">Our Story</h2>
<div className="prose dark:prose-invert max-w-none text-lg text-gray-600 dark:text-gray-300 space-y-6"> <div className="prose dark:prose-invert max-w-none text-lg text-gray-600 dark:text-gray-300 space-y-6">
<p> <p>
Bay Area Affiliates was founded with a simple belief: local businesses deserve the same level of IT expertise and reliability as large corporations, but with the personal touch that only comes from working with your neighbors. Bay Area Affiliates was founded with a simple belief: local businesses deserve the same level of IT expertise and reliability as large corporations, but with the personal touch that only comes from working with your neighbors.
</p> </p>
<p> <p>
Over the years, we've watched the Coastal Bend grow and change. We've helped businesses navigate technology challenges, from the transition to cloud computing to the rapid shift to remote work. Through it all, we've maintained our commitment to clear communication, reliable solutions, and exceptional service. Over the years, we've watched the Coastal Bend grow and change. We've helped businesses navigate technology challenges, from the transition to cloud computing to the rapid shift to remote work. Through it all, we've maintained our commitment to clear communication, reliable solutions, and exceptional service.
</p> </p>
<p> <p>
Today, we're proud to serve over 150 businesses across the region, from Corpus Christi to the smallest coastal communities. Our team combines deep technical expertise with real-world business understanding to deliver IT solutions that actually work for our clients. Today, we're proud to serve over 150 businesses across the region, from Corpus Christi to the smallest coastal communities. Our team combines deep technical expertise with real-world business understanding to deliver IT solutions that actually work for our clients.
</p> </p>
</div> </div>
</div> </div>
</section> </section>
{/* Stats */} {/* Stats */}
<section className="py-16 px-6 bg-white/5 backdrop-blur-sm border-y border-white/5 text-white relative"> <section className="py-16 px-6 bg-white/5 backdrop-blur-sm border-y border-white/5 text-white relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_60%_50%_at_50%_-20%,rgba(255,255,255,0.03),rgba(255,255,255,0))] pointer-events-none"></div> <div className="absolute inset-0 bg-[radial-gradient(ellipse_60%_50%_at_50%_-20%,rgba(255,255,255,0.03),rgba(255,255,255,0))] pointer-events-none"></div>
<div className="max-w-7xl mx-auto grid grid-cols-2 md:grid-cols-4 gap-8 text-center relative z-10"> <div className="max-w-7xl mx-auto grid grid-cols-2 md:grid-cols-4 gap-8 text-center relative z-10">
{[ {[
{ label: 'Businesses served', value: 150, suffix: '+' }, { label: 'Businesses served', value: 150, suffix: '+' },
{ label: 'Uptime achieved', value: 99.9, suffix: '%' }, { label: 'Uptime achieved', value: 99.9, suffix: '%' },
{ label: 'Years of service', value: 15, suffix: '+' }, { label: 'Years of service', value: 15, suffix: '+' },
{ label: 'Response time', value: 2, prefix: '<', suffix: 'min' }, { label: 'Response time', value: 2, prefix: '<', suffix: 'min' },
].map((stat, index) => ( ].map((stat, index) => (
<div key={index} className="p-4"> <div key={index} className="p-4">
<div className="text-4xl md:text-5xl font-bold mb-2 flex justify-center items-center gap-1"> <div className="text-4xl md:text-5xl font-bold mb-2 flex justify-center items-center gap-1">
{stat.prefix && <span>{stat.prefix}</span>} {stat.prefix && <span>{stat.prefix}</span>}
<Counter value={stat.value} /> <Counter value={stat.value} />
{stat.suffix && <span>{stat.suffix}</span>} {stat.suffix && <span>{stat.suffix}</span>}
</div> </div>
<div className="text-gray-400 font-medium">{stat.label}</div> <div className="text-gray-400 font-medium">{stat.label}</div>
</div> </div>
))} ))}
</div> </div>
</section> </section>
{/* Values */} {/* Values */}
<section className="py-24 px-6 bg-gray-50 dark:bg-black/20 relative overflow-hidden"> <section className="py-24 px-6 bg-gray-50 dark:bg-black/20 relative overflow-hidden">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))] pointer-events-none"></div> <div className="absolute inset-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))] pointer-events-none"></div>
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[500px] bg-blue-500/5 dark:bg-white/5 rounded-full blur-[120px] pointer-events-none" /> <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[500px] bg-blue-500/5 dark:bg-white/5 rounded-full blur-[120px] pointer-events-none" />
<div className="max-w-7xl mx-auto relative z-10"> <div className="max-w-7xl mx-auto relative z-10">
<div className="text-center mb-16"> <div className="text-center mb-16">
<h2 className="font-display text-3xl font-bold mb-4 text-gray-900 dark:text-white">Our Values</h2> <h2 className="font-display text-3xl font-bold mb-4 text-gray-900 dark:text-white">Our Values</h2>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8"> <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{values.map((value, index) => ( {values.map((value, index) => (
<motion.div <motion.div
key={index} key={index}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay: index * 0.1 }} transition={{ delay: index * 0.1 }}
whileHover={{ y: -5 }} whileHover={{ y: -5 }}
className="bg-white dark:bg-[#161616] p-8 rounded-2xl shadow-lg border border-gray-100 dark:border-white/5" className="bg-white dark:bg-[#161616] p-8 rounded-2xl shadow-lg border border-gray-100 dark:border-white/5"
> >
<div className="w-12 h-12 bg-gray-100 dark:bg-white/10 rounded-xl flex items-center justify-center mb-6 text-gray-900 dark:text-white"> <div className="w-12 h-12 bg-gray-100 dark:bg-white/10 rounded-xl flex items-center justify-center mb-6 text-gray-900 dark:text-white">
<span className="material-symbols-outlined text-2xl">{value.icon}</span> <span className="material-symbols-outlined text-2xl">{value.icon}</span>
</div> </div>
<h3 className="text-xl font-bold mb-3 text-gray-900 dark:text-white">{value.title}</h3> <h3 className="text-xl font-bold mb-3 text-gray-900 dark:text-white">{value.title}</h3>
<p className="text-gray-600 dark:text-gray-400 leading-relaxed"> <p className="text-gray-600 dark:text-gray-400 leading-relaxed">
{value.desc} {value.desc}
</p> </p>
</motion.div> </motion.div>
))} ))}
</div> </div>
</div> </div>
</section> </section>
{/* Timeline */} {/* Timeline */}
<section className="py-24 px-6 relative" ref={timelineRef}> <section className="py-24 px-6 relative" ref={timelineRef}>
<div className="absolute inset-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))] pointer-events-none"></div> <div className="absolute inset-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))] pointer-events-none"></div>
<div className="max-w-4xl mx-auto relative z-10"> <div className="max-w-4xl mx-auto relative z-10">
<h2 className="font-display text-3xl font-bold mb-12 text-center text-gray-900 dark:text-white">Our Journey</h2> <h2 className="font-display text-3xl font-bold mb-12 text-center text-gray-900 dark:text-white">Our Journey</h2>
<div className="space-y-12 relative"> <div className="space-y-12 relative">
{/* Base Line */} {/* Base Line */}
<div className="absolute left-[19px] md:left-1/2 top-0 bottom-0 w-0.5 bg-gray-200 dark:bg-white/10 -translate-x-1/2 md:translate-x-0"></div> <div className="absolute left-[19px] md:left-1/2 top-0 bottom-0 w-0.5 bg-gray-200 dark:bg-white/10 -translate-x-1/2 md:translate-x-0"></div>
{/* Light Beam Line */} {/* Light Beam Line */}
<motion.div <motion.div
style={{ height: heightTransform }} style={{ height: heightTransform }}
className="absolute left-[19px] md:left-1/2 top-[-60px] w-0.5 bg-gradient-to-b from-black via-black to-transparent dark:from-white dark:via-white dark:to-transparent origin-top shadow-[0_0_25px_2px_rgba(0,0,0,0.5)] dark:shadow-[0_0_25px_2px_rgba(255,255,255,0.7)] -translate-x-1/2 md:translate-x-0 z-10" className="absolute left-[19px] md:left-1/2 top-[-60px] w-0.5 bg-gradient-to-b from-black via-black to-transparent dark:from-white dark:via-white dark:to-transparent origin-top shadow-[0_0_25px_2px_rgba(0,0,0,0.5)] dark:shadow-[0_0_25px_2px_rgba(255,255,255,0.7)] -translate-x-1/2 md:translate-x-0 z-10"
></motion.div> ></motion.div>
{timeline.map((item, index) => ( {timeline.map((item, index) => (
<motion.div <motion.div
key={index} key={index}
initial={{ opacity: 0, x: index % 2 === 0 ? -50 : 50 }} initial={{ opacity: 0, x: index % 2 === 0 ? -50 : 50 }}
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }} transition={{ duration: 0.5, delay: index * 0.1 }}
className={`relative flex flex-col md:flex-row gap-8 ${index % 2 === 0 ? 'md:text-right' : 'md:flex-row-reverse md:text-left'}`} className={`relative flex flex-col md:flex-row gap-8 ${index % 2 === 0 ? 'md:text-right' : 'md:flex-row-reverse md:text-left'}`}
> >
<div className="md:w-1/2 pt-1"> <div className="md:w-1/2 pt-1">
<span className="inline-block px-4 py-1 rounded-full bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-white font-bold text-sm mb-3"> <span className="inline-block px-4 py-1 rounded-full bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-white font-bold text-sm mb-3">
{item.year} {item.year}
</span> </span>
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-2">{item.title}</h3> <h3 className="text-xl font-bold text-gray-900 dark:text-white mb-2">{item.title}</h3>
<p className="text-gray-600 dark:text-gray-400">{item.desc}</p> <p className="text-gray-600 dark:text-gray-400">{item.desc}</p>
</div> </div>
{/* Timeline Dot */} {/* Timeline Dot */}
<div className="absolute left-0 md:left-1/2 -translate-x-[5px] md:-translate-x-1/2 w-10 h-10 flex items-center justify-center z-20"> <div className="absolute left-0 md:left-1/2 -translate-x-[5px] md:-translate-x-1/2 w-10 h-10 flex items-center justify-center z-20">
<motion.div <motion.div
className="w-4 h-4 rounded-full border-2 transition-colors duration-500" className="w-4 h-4 rounded-full border-2 transition-colors duration-500"
animate={index <= activeTimelineIndex ? { animate={index <= activeTimelineIndex ? {
backgroundColor: "#000000", // Will be white in dark mode via class if needed, checking global styles backgroundColor: "#000000", // Will be white in dark mode via class if needed, checking global styles
scale: 1.5, scale: 1.5,
borderColor: "#000000", borderColor: "#000000",
boxShadow: "0 0 20px rgba(0,0,0,0.5)" boxShadow: "0 0 20px rgba(0,0,0,0.5)"
} : { } : {
backgroundColor: "transparent", backgroundColor: "transparent",
scale: 1, scale: 1,
borderColor: "rgba(156, 163, 175, 0.5)", // gray-400 borderColor: "rgba(156, 163, 175, 0.5)", // gray-400
boxShadow: "none" boxShadow: "none"
}} }}
style={{ style={{
// Manual dark mode override since animate doesn't support class names easily without variants // Manual dark mode override since animate doesn't support class names easily without variants
// Using CSS variable or just forcing white for dark mode if we knew the context, // Using CSS variable or just forcing white for dark mode if we knew the context,
// but better to rely on simpler style. // but better to rely on simpler style.
// We will assume 'bg-black dark:bg-white' equivalent behavior is desired. // We will assume 'bg-black dark:bg-white' equivalent behavior is desired.
// To do this simply, we'll set colors that work on both or use a conditional standard div. // To do this simply, we'll set colors that work on both or use a conditional standard div.
// Let's use standard classes for the base and animate scale/shadow. // Let's use standard classes for the base and animate scale/shadow.
}} }}
> >
<div className={`w-full h-full rounded-full ${index <= activeTimelineIndex ? 'bg-black dark:bg-white' : 'bg-gray-200 dark:bg-white/20'}`} /> <div className={`w-full h-full rounded-full ${index <= activeTimelineIndex ? 'bg-black dark:bg-white' : 'bg-gray-200 dark:bg-white/20'}`} />
</motion.div> </motion.div>
</div> </div>
<div className="md:w-1/2"></div> <div className="md:w-1/2"></div>
</motion.div> </motion.div>
))} ))}
</div> </div>
</div> </div>
</section> </section>
<Contact /> <Contact />
</div> </div>
); );
}; };
export default AboutPage; export default AboutPage;

View File

@ -1,104 +1,81 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import Contact from '../../components/Contact'; import { Link } from 'react-router-dom';
import Contact from '../../components/Contact';
const posts = [ import { blogPostData } from '../data/seoData';
{
id: 1, const BlogPage: React.FC = () => {
title: 'Upgrade your HDD to SSD for a big speed boost', useEffect(() => {
excerpt: 'A practical checklist for Corpus Christi business owners considering SSD upgrades, including before/after performance comparisons and cost analysis.', window.scrollTo(0, 0);
image: '/assets/services/desktop-hardware.png', }, []);
category: 'Hardware',
readTime: '5 min read', return (
date: 'Jan 15, 2026' <div className="pt-20 min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
}, <div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" />
{ <div className="absolute top-[300px] left-1/2 -translate-x-1/2 w-[800px] h-[800px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[120px] pointer-events-none" />
id: 2, <section className="py-20 px-6 bg-white dark:bg-[#0f0f0f] border-b border-gray-100 dark:border-white/5 relative bg-transparent">
title: 'Secure your corporate network access with WireGuard VPN', <div className="max-w-4xl mx-auto text-center relative z-10">
excerpt: 'Learn why Corpus Christi businesses are switching to WireGuard VPN for faster, more secure remote access, and how to implement it properly in the Coastal Bend.', <span className="text-blue-600 dark:text-blue-400 font-bold tracking-widest uppercase text-sm mb-3 block">Latest Insights</span>
image: '/assets/services/vpn-setup.png', <h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white">
category: 'Security', Tech insights for the <br /><span className="text-gray-400">Coastal Bend business.</span>
readTime: '7 min read', </h1>
date: 'Jan 10, 2026' </div>
}, </section>
{
id: 3, <section className="py-16 px-6 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
title: 'What comprehensive IT support looks like for SMBs', <div className="max-w-5xl mx-auto space-y-16">
excerpt: 'Understanding the full scope of managed IT services for Corpus Christi small businesses: from hardware and network infrastructure to virtualization and helpdesk support.', {blogPostData.map((post) => (
image: '/assets/services/network-infrastructure.png', <Link
category: 'Strategy', key={post.id}
readTime: '6 min read', to={`/${post.slug}`}
date: 'Jan 05, 2026' className="block"
} >
]; <motion.div
initial={{ opacity: 0, y: 20 }}
const BlogPage: React.FC = () => { whileInView={{ opacity: 1, y: 0 }}
useEffect(() => { viewport={{ once: true, margin: "-100px" }}
window.scrollTo(0, 0); whileHover={{ y: -5 }}
}, []); className="group grid md:grid-cols-2 gap-0 bg-white dark:bg-[#161616] rounded-3xl overflow-hidden shadow-lg border border-gray-100 dark:border-white/5 hover:shadow-2xl hover:shadow-blue-900/10 transition-all duration-300"
>
return ( <div className="h-64 md:h-auto overflow-hidden relative">
<div className="pt-20 min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden"> <img
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" /> src={post.image || '/images/blog/default.png'}
<div className="absolute top-[300px] left-1/2 -translate-x-1/2 w-[800px] h-[800px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[120px] pointer-events-none" /> alt={post.h1}
<section className="py-20 px-6 bg-white dark:bg-[#0f0f0f] border-b border-gray-100 dark:border-white/5 relative bg-transparent"> className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
<div className="max-w-4xl mx-auto text-center relative z-10"> />
<span className="text-blue-600 dark:text-blue-400 font-bold tracking-widest uppercase text-sm mb-3 block">Latest Insights</span> <div className="absolute top-4 left-4">
<h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white"> <span className="px-3 py-1 bg-black/70 backdrop-blur-md text-white text-xs font-bold rounded-full border border-white/20">
Tech insights for the <br /><span className="text-gray-400">Coastal Bend business.</span> {post.category === 'authority' ? 'IT Insights' : 'Local Services'}
</h1> </span>
</div> </div>
</section> </div>
<div className="p-8 md:p-12 flex flex-col justify-center">
<section className="py-16 px-6 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]"> <div className="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400 mb-4">
<div className="max-w-5xl mx-auto space-y-16"> <span>Jan 2026</span>
{posts.map((post) => ( <span className="w-1 h-1 rounded-full bg-gray-300 dark:bg-gray-600"></span>
<motion.div <span>8 min read</span>
key={post.id} </div>
initial={{ opacity: 0, y: 20 }} <h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
whileInView={{ opacity: 1, y: 0 }} {post.h1}
viewport={{ once: true, margin: "-100px" }} </h2>
whileHover={{ y: -5 }} <p className="text-gray-600 dark:text-gray-300 leading-relaxed mb-8">
className="group grid md:grid-cols-2 gap-0 bg-white dark:bg-[#161616] rounded-3xl overflow-hidden shadow-lg border border-gray-100 dark:border-white/5 hover:shadow-2xl hover:shadow-blue-900/10 transition-all duration-300" {post.description}
> </p>
<div className="h-64 md:h-auto overflow-hidden relative"> <div className="mt-auto">
<img <span className="inline-flex items-center gap-2 font-bold text-blue-600 dark:text-white group-hover:gap-3 transition-all">
src={post.image} Read article <span className="material-symbols-outlined text-sm">arrow_forward</span>
alt={post.title} </span>
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" </div>
/> </div>
<div className="absolute top-4 left-4"> </motion.div>
<span className="px-3 py-1 bg-black/70 backdrop-blur-md text-white text-xs font-bold rounded-full border border-white/20"> </Link>
{post.category} ))}
</span> </div>
</div> </section>
</div>
<div className="p-8 md:p-12 flex flex-col justify-center"> <Contact />
<div className="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400 mb-4"> </div>
<span>{post.date}</span> );
<span className="w-1 h-1 rounded-full bg-gray-300 dark:bg-gray-600"></span> };
<span>{post.readTime}</span>
</div> export default BlogPage;
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
{post.title}
</h2>
<p className="text-gray-600 dark:text-gray-300 leading-relaxed mb-8">
{post.excerpt}
</p>
<div className="mt-auto">
<span className="inline-flex items-center gap-2 font-bold text-blue-600 dark:text-white group-hover:gap-3 transition-all">
Read article <span className="material-symbols-outlined text-sm">arrow_forward</span>
</span>
</div>
</div>
</motion.div>
))}
</div>
</section>
<Contact />
</div>
);
};
export default BlogPage;

322
src/pages/BlogPostPage.tsx Normal file
View File

@ -0,0 +1,322 @@
import React, { useEffect, useRef, useLayoutEffect } from 'react';
import { motion, useMotionTemplate, useMotionValue } from 'framer-motion';
import { Link } from 'react-router-dom';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import SEO from '../../components/SEO';
import Services from '../../components/Services';
import CTA from '../../components/CTA';
import AreasWeServe from '../../components/AreasWeServe';
import { BlogPostData } from '../data/seoData';
gsap.registerPlugin(ScrollTrigger);
interface BlogPostPageProps {
data: BlogPostData;
}
const BlogPostPage: React.FC<BlogPostPageProps> = ({ data }) => {
const containerRef = useRef<HTMLDivElement>(null);
const parallaxWrapperRef = useRef<HTMLDivElement>(null);
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const handleMouseMove = ({ currentTarget, clientX, clientY }: React.MouseEvent) => {
const { left, top } = currentTarget.getBoundingClientRect();
mouseX.set(clientX - left);
mouseY.set(clientY - top + 75);
};
useLayoutEffect(() => {
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();
}, []);
useEffect(() => {
window.scrollTo(0, 0);
}, []);
const category = data.slug.includes('corpus-christi-blog') ||
data.slug.includes('portland-tx') ||
data.slug.includes('rockport-tx') ||
data.slug.includes('aransas-pass') ||
data.slug.includes('kingsville-tx')
? 'Local Services'
: 'IT Insights';
return (
<>
<SEO
title={data.title}
description={data.description}
keywords={data.keywords}
canonicalUrl={window.location.href}
/>
<div className="min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
{/* Hero Section */}
<section
ref={containerRef}
onMouseMove={handleMouseMove}
className="relative min-h-screen flex items-center justify-center overflow-hidden pt-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="/src/assets/hero-bg.png"
/>
{/* Highlight Layer */}
<motion.img
style={{
maskImage: useMotionTemplate`radial-gradient(100px circle at ${mouseX}px ${mouseY}px, black, transparent)`,
WebkitMaskImage: useMotionTemplate`radial-gradient(100px circle at ${mouseX}px ${mouseY}px, black, transparent)`,
}}
alt=""
className="absolute inset-0 w-full h-full object-cover mix-blend-screen opacity-100 brightness-150 contrast-150 filter saturate-150"
src="/src/assets/hero-bg.png"
/>
</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">
{/* Breadcrumbs */}
<nav className="hero-stagger mb-8 text-sm">
<ol className="flex items-center gap-2 text-gray-600 dark:text-gray-400 justify-center">
<li>
<Link to="/" className="hover:text-gray-900 dark:hover:text-white transition-colors">
Home
</Link>
</li>
<span className="material-symbols-outlined text-xs">chevron_right</span>
<li>
<Link to="/blog" className="hover:text-gray-900 dark:hover:text-white transition-colors">
Blog
</Link>
</li>
<span className="material-symbols-outlined text-xs">chevron_right</span>
<li className="text-gray-900 dark:text-white font-medium">{category}</li>
</ol>
</nav>
<div className="hero-stagger flex items-center justify-center gap-2 mb-6">
<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">
{category}
</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-6xl lg:text-7xl font-medium tracking-tighter leading-[1.1] mb-8 text-gray-900 dark:text-white">
{data.h1}
</h1>
{/* Meta Info */}
<div className="hero-stagger flex items-center gap-6 text-gray-600 dark:text-gray-400 mb-8 justify-center">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-sm">schedule</span>
<span>5 min read</span>
</div>
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-sm">calendar_today</span>
<span>January 2025</span>
</div>
</div>
{/* Featured Image */}
{data.image && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.4, duration: 0.8 }}
className="hero-stagger rounded-2xl overflow-hidden border border-gray-200 dark:border-white/10 shadow-2xl mb-8 max-w-md mx-auto"
>
<img
src={data.image}
alt={data.h1}
className="w-full h-auto max-h-64 object-cover"
/>
</motion.div>
)}
<div className="hero-stagger flex flex-col sm:flex-row items-center justify-center gap-4">
<motion.a
href="/contact"
className="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: "#3b82f6", color: "#ffffff" }}
whileTap={{ scale: 0.95 }}
>
Get IT Support
</motion.a>
<motion.a
href="/it-support-corpus-christi"
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="px-6 py-16 relative">
<div className="max-w-4xl mx-auto">
<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-12 md:p-16 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-h3:mt-8 prose-h3:mb-4 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 prose-strong:text-gray-900 dark:prose-strong:text-white">
<div dangerouslySetInnerHTML={{ __html: data.content }} />
</div>
</motion.div>
</div>
</section>
{/* CTA Section */}
<section className="px-6 py-16">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="max-w-4xl mx-auto"
>
<div className="p-12 bg-gradient-to-br from-blue-50 to-gray-50 dark:from-blue-950/30 dark:to-gray-950/30 rounded-3xl border border-blue-100 dark:border-blue-900/50 shadow-xl text-center">
<span className="material-symbols-outlined text-blue-600 dark:text-blue-400 text-5xl mb-6 block">
{category === 'Local Services' ? 'location_on' : 'insights'}
</span>
<h2 className="font-display text-3xl font-bold mb-4 text-gray-900 dark:text-white">
{category === 'Local Services'
? 'Ready to Get IT Support in Your Area?'
: 'Need Expert IT Support for Your Business?'}
</h2>
<p className="text-lg text-gray-700 dark:text-gray-300 mb-8 max-w-2xl mx-auto">
{category === 'Local Services'
? 'Contact us today to learn how we can help your business with reliable IT support and managed services.'
: 'Let us handle your IT needs so you can focus on growing your business. Get a free consultation today.'}
</p>
<Link
to="/contact"
className="inline-flex items-center gap-3 px-10 py-5 bg-black dark:bg-white text-white dark:text-black rounded-full font-bold text-lg transition-all hover:scale-105 shadow-2xl hover:shadow-3xl"
>
Get Started
<span className="material-symbols-outlined">arrow_forward</span>
</Link>
</div>
</motion.div>
</section>
{/* Related Content Grid */}
<section className="px-6 py-16">
<div className="max-w-6xl mx-auto">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="font-display text-4xl font-bold mb-12 text-center text-gray-900 dark:text-white"
>
Why Choose Bay Area IT?
</motion.h2>
<div className="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">
verified_user
</span>
<h3 className="font-display text-xl font-bold mb-3 text-gray-900 dark:text-white">
Proven Expertise
</h3>
<p className="text-gray-600 dark:text-gray-300">
Years of experience serving businesses across the Coastal Bend with comprehensive IT solutions.
</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">
support_agent
</span>
<h3 className="font-display text-xl font-bold mb-3 text-gray-900 dark:text-white">
24/7 Support
</h3>
<p className="text-gray-600 dark:text-gray-300">
Round-the-clock monitoring and support to keep your business running smoothly at all times.
</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">
handshake
</span>
<h3 className="font-display text-xl font-bold mb-3 text-gray-900 dark:text-white">
Local Partnership
</h3>
<p className="text-gray-600 dark:text-gray-300">
A trusted local partner who understands your community and business needs.
</p>
</motion.div>
</div>
</div>
</section>
{/* Services Section */}
<Services preview={true} />
{/* Areas We Serve & CTA */}
<AreasWeServe />
<CTA />
</div>
</>
);
};
export default BlogPostPage;

View File

@ -1,207 +1,207 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
const ContactPage: React.FC = () => { const ContactPage: React.FC = () => {
useEffect(() => { useEffect(() => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
}, []); }, []);
const faqs = [ const faqs = [
{ q: 'How quickly can you start?', a: 'Most assessments can begin within 48 hours of contact. Emergency support is available 24/7.' }, { q: 'How quickly can you start?', a: 'Most assessments can begin within 48 hours of contact. Emergency support is available 24/7.' },
{ q: 'How do you price services?', a: 'Transparent monthly pricing based on devices and services needed. No hidden fees or surprise charges.' }, { q: 'How do you price services?', a: 'Transparent monthly pricing based on devices and services needed. No hidden fees or surprise charges.' },
{ q: 'What\'s included in support?', a: '24/7 monitoring, helpdesk, proactive maintenance, security updates, and SLA guarantees.' }, { q: 'What\'s included in support?', a: '24/7 monitoring, helpdesk, proactive maintenance, security updates, and SLA guarantees.' },
]; ];
return ( return (
<div className="pt-20 min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden"> <div className="pt-20 min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" /> <div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" />
<div className="absolute bottom-0 right-0 w-[500px] h-[500px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[100px] pointer-events-none" /> <div className="absolute bottom-0 right-0 w-[500px] h-[500px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[100px] pointer-events-none" />
{/* Hero */} {/* Hero */}
<section className="py-20 px-6 text-center border-b border-white/5 relative z-10"> <section className="py-20 px-6 text-center border-b border-white/5 relative z-10">
<h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-white"> <h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-white">
Let's talk about <br /><span className="text-gray-500">your IT needs</span> Let's talk about <br /><span className="text-gray-500">your IT needs</span>
</h1> </h1>
<p className="text-xl text-gray-400 max-w-2xl mx-auto"> <p className="text-xl text-gray-400 max-w-2xl mx-auto">
Ready to improve your technology? We're here to help. Get started with a free consultation and see how we can make your IT work better for you. Ready to improve your technology? We're here to help. Get started with a free consultation and see how we can make your IT work better for you.
</p> </p>
</section> </section>
<section className="py-24 px-6 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]"> <section className="py-24 px-6 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16"> <div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16">
{/* Left: Contact Form */} {/* Left: Contact Form */}
<motion.div <motion.div
initial={{ opacity: 0, x: -20 }} initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
className="bg-white/5 backdrop-blur-sm p-8 md:p-10 rounded-3xl shadow-xl border border-white/10" className="bg-white/5 backdrop-blur-sm p-8 md:p-10 rounded-3xl shadow-xl border border-white/10"
> >
<h3 className="text-2xl font-bold mb-8 text-white">Send us a message</h3> <h3 className="text-2xl font-bold mb-8 text-white">Send us a message</h3>
<form className="space-y-6"> <form className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">Name *</label> <label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">Name *</label>
<input type="text" id="name" required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="John Doe" /> <input type="text" id="name" required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="John Doe" />
</div> </div>
<div> <div>
<label htmlFor="phone" className="block text-sm font-medium text-gray-300 mb-2">Phone</label> <label htmlFor="phone" className="block text-sm font-medium text-gray-300 mb-2">Phone</label>
<input type="tel" id="phone" className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="(555) 000-0000" /> <input type="tel" id="phone" className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="(555) 000-0000" />
</div> </div>
</div> </div>
<div> <div>
<label htmlFor="email" className="block text-sm font-medium text-gray-300 mb-2">Email *</label> <label htmlFor="email" className="block text-sm font-medium text-gray-300 mb-2">Email *</label>
<input type="email" id="email" required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="john@company.com" /> <input type="email" id="email" required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="john@company.com" />
</div> </div>
<div> <div>
<label htmlFor="company" className="block text-sm font-medium text-gray-300 mb-2">Company</label> <label htmlFor="company" className="block text-sm font-medium text-gray-300 mb-2">Company</label>
<input type="text" id="company" className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="Acme Inc." /> <input type="text" id="company" className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="Acme Inc." />
</div> </div>
<div> <div>
<label htmlFor="message" className="block text-sm font-medium text-gray-300 mb-2">Message *</label> <label htmlFor="message" className="block text-sm font-medium text-gray-300 mb-2">Message *</label>
<textarea id="message" rows={4} required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600 resize-none" placeholder="How can we help you?"></textarea> <textarea id="message" rows={4} required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600 resize-none" placeholder="How can we help you?"></textarea>
</div> </div>
<button type="submit" className="w-full py-4 bg-white text-black font-bold rounded-xl transition-all hover:bg-gray-200 hover:scale-[1.02] shadow-[0_0_20px_rgba(255,255,255,0.1)]"> <button type="submit" className="w-full py-4 bg-white text-black font-bold rounded-xl transition-all hover:bg-gray-200 hover:scale-[1.02] shadow-[0_0_20px_rgba(255,255,255,0.1)]">
Send Message Send Message
</button> </button>
</form> </form>
</motion.div> </motion.div>
{/* Right: FAQ & Info */} {/* Right: FAQ & Info */}
<motion.div <motion.div
initial={{ opacity: 0, x: 20 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
className="space-y-12" className="space-y-12"
> >
{/* Contact Info */} {/* Contact Info */}
<div className="grid sm:grid-cols-2 gap-8"> <div className="grid sm:grid-cols-2 gap-8">
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors"> <div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors">
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4"> <div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4">
<span className="material-symbols-outlined">call</span> <span className="material-symbols-outlined">call</span>
</div> </div>
<h4 className="font-bold text-white mb-1">Phone</h4> <h4 className="font-bold text-white mb-1">Phone</h4>
<p className="text-gray-400">(361) 765-8400</p> <p className="text-gray-400">(361) 765-8400</p>
</div> </div>
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors"> <div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors">
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4"> <div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4">
<span className="material-symbols-outlined">location_on</span> <span className="material-symbols-outlined">location_on</span>
</div> </div>
<h4 className="font-bold text-white mb-1">Address</h4> <h4 className="font-bold text-white mb-1">Address</h4>
<p className="text-gray-400">1001 Blucher St,<br />Corpus Christi, TX 78401</p> <p className="text-gray-400">1001 Blucher St,<br />Corpus Christi, TX 78401</p>
</div> </div>
</div> </div>
<div className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10"> <div className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10">
<h4 className="font-bold text-white mb-4">Hours & Area</h4> <h4 className="font-bold text-white mb-4">Hours & Area</h4>
<p className="text-gray-400 mb-2"><span className="font-semibold text-white">Mon - Fri:</span> 8:00 AM - 6:00 PM</p> <p className="text-gray-400 mb-2"><span className="font-semibold text-white">Mon - Fri:</span> 8:00 AM - 6:00 PM</p>
<p className="text-gray-500 text-sm mb-4">(Emergency support: 24/7)</p> <p className="text-gray-500 text-sm mb-4">(Emergency support: 24/7)</p>
<p className="text-gray-400"><span className="font-semibold text-white">Service Area:</span> Corpus Christi & the Coastal Bend (including Portland, Rockport, Aransas Pass, Kingsville, Port Aransas)</p> <p className="text-gray-400"><span className="font-semibold text-white">Service Area:</span> Corpus Christi & the Coastal Bend (including Portland, Rockport, Aransas Pass, Kingsville, Port Aransas)</p>
</div> </div>
{/* FAQ */} {/* FAQ */}
<div> <div>
<h3 className="text-2xl font-bold mb-6 text-white">Quick Answers</h3> <h3 className="text-2xl font-bold mb-6 text-white">Quick Answers</h3>
<div className="space-y-4"> <div className="space-y-4">
{faqs.map((faq, index) => ( {faqs.map((faq, index) => (
<div key={index} className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/30 transition-colors"> <div key={index} className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/30 transition-colors">
<h4 className="font-bold text-white mb-2">{faq.q}</h4> <h4 className="font-bold text-white mb-2">{faq.q}</h4>
<p className="text-gray-400 text-sm leading-relaxed">{faq.a}</p> <p className="text-gray-400 text-sm leading-relaxed">{faq.a}</p>
</div> </div>
))} ))}
</div> </div>
</div> </div>
</motion.div> </motion.div>
</div> </div>
</section> </section>
{/* What Happens Next Section */} {/* What Happens Next Section */}
<section className="py-24 px-6 border-t border-white/5"> <section className="py-24 px-6 border-t border-white/5">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<h2 className="font-display text-3xl md:text-4xl font-bold text-center mb-16 text-white"> <h2 className="font-display text-3xl md:text-4xl font-bold text-center mb-16 text-white">
What happens next? What happens next?
</h2> </h2>
<div className="grid md:grid-cols-3 gap-8"> <div className="grid md:grid-cols-3 gap-8">
{[ {[
{ {
step: "01", step: "01",
title: "We respond quickly", title: "We respond quickly",
description: "Get a response within 24 hours, usually much faster." description: "Get a response within 24 hours, usually much faster."
}, },
{ {
step: "02", step: "02",
title: "Free consultation", title: "Free consultation",
description: "20-minute call to understand your needs and challenges." description: "20-minute call to understand your needs and challenges."
}, },
{ {
step: "03", step: "03",
title: "Custom proposal", title: "Custom proposal",
description: "Tailored solution with clear next steps and pricing." description: "Tailored solution with clear next steps and pricing."
} }
].map((item, index) => ( ].map((item, index) => (
<motion.div <motion.div
key={index} key={index}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay: index * 0.1 }} transition={{ delay: index * 0.1 }}
className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10 hover:bg-white/10 transition-colors relative overflow-hidden group" className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10 hover:bg-white/10 transition-colors relative overflow-hidden group"
> >
<div className="absolute top-0 right-0 p-8 opacity-10 font-display text-8xl font-bold text-white group-hover:scale-110 transition-transform select-none pointer-events-none"> <div className="absolute top-0 right-0 p-8 opacity-10 font-display text-8xl font-bold text-white group-hover:scale-110 transition-transform select-none pointer-events-none">
{item.step} {item.step}
</div> </div>
<div className="relative z-10"> <div className="relative z-10">
<div className="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center text-white mb-6 border border-white/10"> <div className="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center text-white mb-6 border border-white/10">
<span className="material-symbols-outlined"> <span className="material-symbols-outlined">
{index === 0 ? 'schedule_send' : index === 1 ? 'phone_in_talk' : 'contract_edit'} {index === 0 ? 'schedule_send' : index === 1 ? 'phone_in_talk' : 'contract_edit'}
</span> </span>
</div> </div>
<h3 className="text-xl font-bold text-white mb-3">{item.title}</h3> <h3 className="text-xl font-bold text-white mb-3">{item.title}</h3>
<p className="text-gray-400 leading-relaxed">{item.description}</p> <p className="text-gray-400 leading-relaxed">{item.description}</p>
</div> </div>
</motion.div> </motion.div>
))} ))}
</div> </div>
</div> </div>
</section> </section>
{/* Service Area Section */} {/* Service Area Section */}
<section className="py-24 px-6 relative overflow-hidden"> <section className="py-24 px-6 relative overflow-hidden">
<div className="absolute inset-0 bg-white/5"></div> <div className="absolute inset-0 bg-white/5"></div>
<div className="max-w-7xl mx-auto relative z-10"> <div className="max-w-7xl mx-auto relative z-10">
<div className="grid lg:grid-cols-2 gap-16 items-center"> <div className="grid lg:grid-cols-2 gap-16 items-center">
<div> <div>
<h2 className="font-display text-3xl md:text-4xl font-bold mb-6 text-white"> <h2 className="font-display text-3xl md:text-4xl font-bold mb-6 text-white">
Our Service Area Our Service Area
</h2> </h2>
<p className="text-lg text-gray-400 mb-8 leading-relaxed"> <p className="text-lg text-gray-400 mb-8 leading-relaxed">
Proudly serving Corpus Christi, Portland, Rockport, Aransas Pass, Kingsville, Port Aransas, and the entire Coastal Bend region. Proudly serving Corpus Christi, Portland, Rockport, Aransas Pass, Kingsville, Port Aransas, and the entire Coastal Bend region.
</p> </p>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
{['Corpus Christi', 'Portland', 'Rockport', 'Aransas Pass', 'Kingsville', 'Port Aransas'].map((city) => ( {['Corpus Christi', 'Portland', 'Rockport', 'Aransas Pass', 'Kingsville', 'Port Aransas'].map((city) => (
<div key={city} className="flex items-center gap-2 px-4 py-2 rounded-full bg-white/5 border border-white/10 text-gray-300"> <div key={city} className="flex items-center gap-2 px-4 py-2 rounded-full bg-white/5 border border-white/10 text-gray-300">
<span className="material-symbols-outlined text-sm text-white">location_on</span> <span className="material-symbols-outlined text-sm text-white">location_on</span>
{city} {city}
</div> </div>
))} ))}
</div> </div>
</div> </div>
<div className="h-[400px] bg-[#1a1a1a] rounded-3xl overflow-hidden border border-white/10 shadow-2xl relative"> <div className="h-[400px] bg-[#1a1a1a] rounded-3xl overflow-hidden border border-white/10 shadow-2xl relative">
<iframe <iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3532.1843778537446!2d-97.39864222453538!3d27.79426697613324!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x86685f8090787b65%3A0x6762397985799732!2s1001%20Blucher%20St%2C%20Corpus%20Christi%2C%20TX%2078401!5e0!3m2!1sen!2sus!4v1709420000000!5m2!1sen!2sus" src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3532.1843778537446!2d-97.39864222453538!3d27.79426697613324!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x86685f8090787b65%3A0x6762397985799732!2s1001%20Blucher%20St%2C%20Corpus%20Christi%2C%20TX%2078401!5e0!3m2!1sen!2sus!4v1709420000000!5m2!1sen!2sus"
width="100%" width="100%"
height="100%" height="100%"
style={{ border: 0 }} style={{ border: 0 }}
allowFullScreen allowFullScreen
loading="lazy" loading="lazy"
referrerPolicy="no-referrer-when-downgrade" referrerPolicy="no-referrer-when-downgrade"
></iframe> ></iframe>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
</div> </div>
); );
}; };
export default ContactPage; export default ContactPage;

View File

@ -1,28 +1,92 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import Hero from '../../components/Hero'; import Hero from '../../components/Hero';
import Mission from '../../components/Mission'; import Mission from '../../components/Mission';
import Services from '../../components/Services'; import Services from '../../components/Services';
import Process from '../../components/Process'; import Process from '../../components/Process';
import Blog from '../../components/Blog'; import Blog from '../../components/Blog';
import Testimonials from '../../components/Testimonials'; import Testimonials from '../../components/Testimonials';
import CTA from '../../components/CTA'; import CTA from '../../components/CTA';
import SEO from '../../components/SEO';
const HomePage: React.FC = () => { import FAQ from '../../components/FAQ';
useEffect(() => { import AreasWeServe from '../../components/AreasWeServe';
window.scrollTo(0, 0); import { locationData } from '../data/seoData';
}, []);
const HomePage: React.FC = () => {
return ( useEffect(() => {
<> window.scrollTo(0, 0);
<Hero /> }, []);
<Mission />
<Services preview={true} /> // Enhanced LocalBusiness Schema per SEO plan
<Process /> const schema = {
<Blog /> "@context": "https://schema.org",
<Testimonials /> "@type": "ITService",
<CTA /> "name": "Bay Area IT Services",
</> "image": "https://bayarea-cc.com/logo.png",
); "@id": "https://bayarea-cc.com",
}; "url": "https://bayarea-cc.com",
"telephone": "+1-361-XXX-XXXX", // TODO: Replace with actual phone
export default HomePage; "priceRange": "$$",
"address": {
"@type": "PostalAddress",
"streetAddress": "[YOUR STREET]", // TODO: Add actual address
"addressLocality": "Corpus Christi",
"addressRegion": "TX",
"postalCode": "[YOUR ZIP]", // TODO: Add actual ZIP
"addressCountry": "US"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 27.800583,
"longitude": -97.39638
},
"areaServed": [
{ "@type": "City", "name": "Corpus Christi" },
{ "@type": "City", "name": "Portland" },
{ "@type": "City", "name": "Rockport" },
{ "@type": "City", "name": "Aransas Pass" },
{ "@type": "City", "name": "Kingsville" }
],
"serviceType": [
"IT Support",
"Business IT Support",
"Outsourced IT Services",
"Computer Network Support",
"Cyber Security"
],
"openingHoursSpecification": {
"@type": "OpeningHoursSpecification",
"dayOfWeek": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday"
],
"opens": "08:00",
"closes": "18:00"
}
};
return (
<>
<SEO
title="IT Service & IT Support for Businesses in Corpus Christi, TX"
description="Reliable IT support and IT services for businesses in Corpus Christi, TX. Fast response, outsourced IT support & help desk solutions. Call now."
keywords={["IT Service", "IT Support", "Corpus Christi", "Business IT Support"]}
canonicalUrl={window.location.href}
schema={schema}
/>
<Hero />
<Mission />
<Services preview={true} />
<Process />
<Blog />
<Testimonials />
<AreasWeServe />
<FAQ items={locationData[0].faq} />
<CTA />
</>
);
};
export default HomePage;

214
src/pages/LocationPage.tsx Normal file
View File

@ -0,0 +1,214 @@
import React, { useEffect, useRef, useLayoutEffect } from 'react';
import { motion, useMotionTemplate, useMotionValue } from 'framer-motion';
import { Link } from 'react-router-dom';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import SEO from '../../components/SEO';
import Services from '../../components/Services';
import CTA from '../../components/CTA';
import FAQ from '../../components/FAQ';
import AreasWeServe from '../../components/AreasWeServe';
import { LocationData } from '../data/seoData';
gsap.registerPlugin(ScrollTrigger);
interface LocationPageProps {
data: LocationData;
}
const LocationPage: React.FC<LocationPageProps> = ({ data }) => {
const containerRef = useRef<HTMLDivElement>(null);
const parallaxWrapperRef = useRef<HTMLDivElement>(null);
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const handleMouseMove = ({ currentTarget, clientX, clientY }: React.MouseEvent) => {
const { left, top } = currentTarget.getBoundingClientRect();
mouseX.set(clientX - left);
mouseY.set(clientY - top + 75);
};
useLayoutEffect(() => {
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();
}, []);
useEffect(() => {
window.scrollTo(0, 0);
}, []);
const schema = {
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Bay Area IT Services",
"url": window.location.href,
"areaServed": {
"@type": "City",
"name": data.city
}
};
return (
<>
<SEO
title={data.title}
description={data.description}
keywords={data.keywords}
canonicalUrl={window.location.href}
schema={schema}
/>
<div className="min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
{/* Hero Section */}
<section
ref={containerRef}
onMouseMove={handleMouseMove}
className="relative min-h-[90vh] flex items-center justify-center overflow-hidden pt-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="/src/assets/hero-bg.png"
/>
{/* Highlight Layer */}
<motion.img
style={{
maskImage: useMotionTemplate`radial-gradient(100px circle at ${mouseX}px ${mouseY}px, black, transparent)`,
WebkitMaskImage: useMotionTemplate`radial-gradient(100px circle at ${mouseX}px ${mouseY}px, black, transparent)`,
}}
alt=""
className="absolute inset-0 w-full h-full object-cover mix-blend-screen opacity-100 brightness-150 contrast-150 filter saturate-150"
src="/src/assets/hero-bg.png"
/>
</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">
{data.city}, Texas IT Support
</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="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: "#3b82f6", color: "#ffffff" }}
whileTap={{ scale: 0.95 }}
>
Get IT Support
</motion.a>
<motion.a
href="/it-support-corpus-christi"
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="px-6 py-16 relative">
<div className="max-w-4xl mx-auto">
<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-12 md:p-16 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>
{/* Internal Linking Card (if not Corpus Christi) */}
{data.city !== "Corpus Christi" && (
<section className="px-6 py-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="max-w-4xl mx-auto"
>
<div className="p-10 bg-gradient-to-br from-blue-50 to-gray-50 dark:from-blue-950/30 dark:to-gray-950/30 rounded-3xl border border-blue-100 dark:border-blue-900/50 shadow-xl">
<div className="flex items-start gap-4">
<span className="material-symbols-outlined text-blue-600 dark:text-blue-400 text-4xl">
info
</span>
<div>
<h3 className="font-display text-2xl font-bold mb-3 text-gray-900 dark:text-white">
Serving the Coastal Bend
</h3>
<p className="text-lg text-gray-700 dark:text-gray-300 leading-relaxed">
We are based in Corpus Christi and proudly serve businesses throughout the entire surrounding area.
Learn more about our <Link to="/it-support-corpus-christi" className="text-blue-600 dark:text-blue-400 font-bold hover:underline">IT support in Corpus Christi</Link> and our commitment to local businesses.
</p>
</div>
</div>
</div>
</motion.div>
</section>
)}
{/* Services Section */}
<Services featuredIds={[10, 9, 11]} />
{/* Areas We Serve & CTA */}
<AreasWeServe />
<FAQ items={data.faq} />
<CTA />
</div>
</>
);
};
export default LocationPage;

232
src/pages/ServicePage.tsx Normal file
View File

@ -0,0 +1,232 @@
import React, { useEffect, useRef, useLayoutEffect } from 'react';
import { motion, useMotionTemplate, useMotionValue } from 'framer-motion';
import { Link } from 'react-router-dom';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import SEO from '../../components/SEO';
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';
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 handleMouseMove = ({ currentTarget, clientX, clientY }: React.MouseEvent) => {
const { left, top } = currentTarget.getBoundingClientRect();
mouseX.set(clientX - left);
mouseY.set(clientY - top + 75);
};
useLayoutEffect(() => {
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();
}, []);
useEffect(() => {
window.scrollTo(0, 0);
}, []);
return (
<>
<SEO
title={data.title}
description={data.description}
keywords={data.keywords}
canonicalUrl={window.location.href}
/>
<div className="min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
{/* Hero Section */}
<section
ref={containerRef}
onMouseMove={handleMouseMove}
className="relative min-h-[90vh] flex items-center justify-center overflow-hidden pt-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="/src/assets/hero-bg.png"
/>
{/* Highlight Layer */}
<motion.img
style={{
maskImage: useMotionTemplate`radial-gradient(100px circle at ${mouseX}px ${mouseY}px, black, transparent)`,
WebkitMaskImage: useMotionTemplate`radial-gradient(100px circle at ${mouseX}px ${mouseY}px, black, transparent)`,
}}
alt=""
className="absolute inset-0 w-full h-full object-cover mix-blend-screen opacity-100 brightness-150 contrast-150 filter saturate-150"
src="/src/assets/hero-bg.png"
/>
</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="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: "#3b82f6", color: "#ffffff" }}
whileTap={{ scale: 0.95 }}
>
Get Started
</motion.a>
<motion.a
href="/it-support-corpus-christi"
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="px-6 py-16 relative">
<div className="max-w-4xl mx-auto">
<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-12 md:p-16 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;

View File

@ -1,414 +1,415 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence, useScroll, useTransform, useMotionValueEvent } from 'framer-motion'; import { motion, AnimatePresence, useScroll, useTransform, useMotionValueEvent } from 'framer-motion';
import Contact from '../../components/Contact'; import Contact from '../../components/Contact';
const services = [ const services = [
{ {
id: 1, id: 1,
title: 'Windows 11 Transition', title: 'Windows 11 Transition',
description: 'Upgrade to Windows 11 before October 2025 to ensure continued security support and take advantage of the latest features.', description: 'Upgrade to Windows 11 before October 2025 to ensure continued security support and take advantage of the latest features.',
challenge: 'Running outdated operating systems leaves your business vulnerable to security threats and compatibility issues.', challenge: 'Running outdated operating systems leaves your business vulnerable to security threats and compatibility issues.',
approach: 'We manage the entire migration process, from hardware compatibility checks to software deployment and user training.', approach: 'We manage the entire migration process, from hardware compatibility checks to software deployment and user training.',
deliverables: [ deliverables: [
'Hardware compatibility assessment', 'Hardware compatibility assessment',
'Windows 11 deployment and configuration', 'Windows 11 deployment and configuration',
'Application compatibility testing', 'Application compatibility testing',
'Security policy implementation', 'Security policy implementation',
'User training sessions' 'User training sessions'
], ],
needs: [ needs: [
'Current device inventory', 'Current device inventory',
'Software list', 'Software list',
'User schedule for upgrades' 'User schedule for upgrades'
], ],
icon: 'desktop_windows', icon: 'desktop_windows',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBMpd_cFINnFibfNErBs8OVAAyDQYTRXix88YH91QImuGi11XGwlY_QUB2R9htcC1h_fTXUeftdEieGT-oi5p5TBjpAyW-86mSsXu-rqhRTBsJlAGuE37bxJES4DUayktXIToEcF-M4PyXdyyTPIYtpYrxK18b2-sPwMzuzCL0LpgJwd5EoYxAkrJQ7W4eBrIG2e9Cw9sY0dJpXJy-TRgwBG0nk-S7W4Y0s3U9w--AzE4fcUimeGMqWwdCncU5tnETmkrkDNFiCyKSA' image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBMpd_cFINnFibfNErBs8OVAAyDQYTRXix88YH91QImuGi11XGwlY_QUB2R9htcC1h_fTXUeftdEieGT-oi5p5TBjpAyW-86mSsXu-rqhRTBsJlAGuE37bxJES4DUayktXIToEcF-M4PyXdyyTPIYtpYrxK18b2-sPwMzuzCL0LpgJwd5EoYxAkrJQ7W4eBrIG2e9Cw9sY0dJpXJy-TRgwBG0nk-S7W4Y0s3U9w--AzE4fcUimeGMqWwdCncU5tnETmkrkDNFiCyKSA'
}, },
{ {
id: 2, id: 2,
title: 'Web Services', title: 'Web Services',
description: 'Web design, domain registration, email services, and more to establish and enhance your online presence.', description: 'Web design, domain registration, email services, and more to establish and enhance your online presence.',
challenge: 'A poor online presence can cost you customers and credibility in a digital-first world.', challenge: 'A poor online presence can cost you customers and credibility in a digital-first world.',
approach: 'We build professional, responsive websites and manage your digital identity to attract and retain customers.', approach: 'We build professional, responsive websites and manage your digital identity to attract and retain customers.',
deliverables: [ deliverables: [
'Custom website design & development', 'Custom website design & development',
'Domain registration & management', 'Domain registration & management',
'Professional email setup (M365/Google)', 'Professional email setup (M365/Google)',
'SEO optimization basics', 'SEO optimization basics',
'Hosting & maintenance' 'Hosting & maintenance'
], ],
needs: [ needs: [
'Brand guidelines / Logo', 'Brand guidelines / Logo',
'Content & copy', 'Content & copy',
'Domain access (if existing)' 'Domain access (if existing)'
], ],
icon: 'language', icon: 'language',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCxibXNCB5mU7MdWE5znMWnQUc9-d2ZoYF7LXK1CMssnvaFz2ZsGzyxXMbqDmely-UfxapqILD5-Exeo1wlQZKg8T2MK4vjlyAMaehoJoqTy2hHh8rxj46i8CKb4-ILL2JswBc98nJt_Fo1DfcDH0dHH5Zz6H4R2Jm1deViSW8Sp2zNp1sTc4eRHy1URiSRQFcr1C8rca6dKiuNDuyDiUmmesqHobXGItaBeFjJC-0OatWpKbr0zF-Y5qvk9Yl5FY2KUcDY9AcTfelu' image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCxibXNCB5mU7MdWE5znMWnQUc9-d2ZoYF7LXK1CMssnvaFz2ZsGzyxXMbqDmely-UfxapqILD5-Exeo1wlQZKg8T2MK4vjlyAMaehoJoqTy2hHh8rxj46i8CKb4-ILL2JswBc98nJt_Fo1DfcDH0dHH5Zz6H4R2Jm1deViSW8Sp2zNp1sTc4eRHy1URiSRQFcr1C8rca6dKiuNDuyDiUmmesqHobXGItaBeFjJC-0OatWpKbr0zF-Y5qvk9Yl5FY2KUcDY9AcTfelu'
}, },
{ {
id: 3, id: 3,
title: 'Performance Upgrades', title: 'Performance Upgrades',
description: 'Enhance your desktops and laptops with SSDs, maintain your Windows installations, and achieve dramatic performance boosts.', description: 'Enhance your desktops and laptops with SSDs, maintain your Windows installations, and achieve dramatic performance boosts.',
challenge: 'Slow computers kill productivity and frustrate employees, leading to wasted time.', challenge: 'Slow computers kill productivity and frustrate employees, leading to wasted time.',
approach: 'We breathe new life into existing hardware with cost-effective upgrades and optimizations.', approach: 'We breathe new life into existing hardware with cost-effective upgrades and optimizations.',
deliverables: [ deliverables: [
'SSD installation & cloning', 'SSD installation & cloning',
'RAM upgrades', 'RAM upgrades',
'System cleanup & optimization', 'System cleanup & optimization',
'Thermal paste replacement', 'Thermal paste replacement',
'Benchmark reporting' 'Benchmark reporting'
], ],
needs: [ needs: [
'Access to devices', 'Access to devices',
'Data backup confirmation' 'Data backup confirmation'
], ],
icon: 'speed', icon: 'speed',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBs2fGGwp4EkMarA9Uvy7IOqyW0Pzxzt-94Bsr8Tkbem4uHPq-vMEmGgKuEmds2zKwPrw2nVcvL3MjjKYWieLSLh5pVUbbK6T9aDxt2xhvo4trARZobhzoQCJfI-r6aGW_aqfwC5XxOr9VA3YdnNnYEgkfW_TWrUWYa6mD8X0KdVG3sLimA8p7qWxIqUzFFV82twn60rP4OwLdIsc6t1OGnJzjemxL1Aw05aDo6Ckfr0a1oZ2kD4xKeTkG--zUhezvXB9I03l6f3b46' image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBs2fGGwp4EkMarA9Uvy7IOqyW0Pzxzt-94Bsr8Tkbem4uHPq-vMEmGgKuEmds2zKwPrw2nVcvL3MjjKYWieLSLh5pVUbbK6T9aDxt2xhvo4trARZobhzoQCJfI-r6aGW_aqfwC5XxOr9VA3YdnNnYEgkfW_TWrUWYa6mD8X0KdVG3sLimA8p7qWxIqUzFFV82twn60rP4OwLdIsc6t1OGnJzjemxL1Aw05aDo6Ckfr0a1oZ2kD4xKeTkG--zUhezvXB9I03l6f3b46'
}, },
{ {
id: 4, id: 4,
title: 'Printer & Scanner Installation', title: 'Printer & Scanner Installation',
description: 'Professional installation and configuration of printers and scanners to ensure seamless integration into your workflow.', description: 'Professional installation and configuration of printers and scanners to ensure seamless integration into your workflow.',
challenge: 'Printer connectivity issues are a leading cause of office support tickets and downtime.', challenge: 'Printer connectivity issues are a leading cause of office support tickets and downtime.',
approach: 'We set up reliable printing environments with proper drivers, networking, and user access controls.', approach: 'We set up reliable printing environments with proper drivers, networking, and user access controls.',
deliverables: [ deliverables: [
'Network printer setup', 'Network printer setup',
'Scanner configuration (Scan-to-Email/Folder)', 'Scanner configuration (Scan-to-Email/Folder)',
'Print server management', 'Print server management',
'One-click user deployment', 'One-click user deployment',
'Troubleshooting training' 'Troubleshooting training'
], ],
needs: [ needs: [
'Printer/Scanner hardware', 'Printer/Scanner hardware',
'Network access details' 'Network access details'
], ],
icon: 'print', icon: 'print',
image: '' image: ''
}, },
{ {
id: 5, id: 5,
title: 'New/Refurbished Desktop Hardware', title: 'New/Refurbished Desktop Hardware',
description: 'Supply and installation of new or refurbished desktop hardware, tailored to meet your business requirements.', description: 'Supply and installation of new or refurbished desktop hardware, tailored to meet your business requirements.',
challenge: 'Sourcing the right hardware at the right price can be time-consuming and risky.', challenge: 'Sourcing the right hardware at the right price can be time-consuming and risky.',
approach: 'We source high-quality new and refurbished equipment that meets your specs and budget, fully tested and ready to go.', approach: 'We source high-quality new and refurbished equipment that meets your specs and budget, fully tested and ready to go.',
deliverables: [ deliverables: [
'Hardware procurement', 'Hardware procurement',
'Quality assurance testing', 'Quality assurance testing',
'Image deployment', 'Image deployment',
'Peripherals setup', 'Peripherals setup',
'Warranty management' 'Warranty management'
], ],
needs: [ needs: [
'Budget constraints', 'Budget constraints',
'Performance requirements' 'Performance requirements'
], ],
icon: 'computer', icon: 'computer',
image: '' image: ''
}, },
{ {
id: 6, id: 6,
title: 'VPN Setup', title: 'VPN Setup',
description: 'Configure Virtual Private Networks to allow secure remote access to your internal network from anywhere.', description: 'Configure Virtual Private Networks to allow secure remote access to your internal network from anywhere.',
challenge: 'Remote work requires secure access to internal resources without exposing your network to threats.', challenge: 'Remote work requires secure access to internal resources without exposing your network to threats.',
approach: 'We implement robust VPN solutions like WireGuard or OpenVPN for secure, encrypted remote connectivity.', approach: 'We implement robust VPN solutions like WireGuard or OpenVPN for secure, encrypted remote connectivity.',
deliverables: [ deliverables: [
'VPN server configuration', 'VPN server configuration',
'Client software deployment', 'Client software deployment',
'Access control lists', 'Access control lists',
'Connection testing', 'Connection testing',
'User guides' 'User guides'
], ],
needs: [ needs: [
'Public IP / DNS details', 'Public IP / DNS details',
'User list' 'User list'
], ],
icon: 'vpn_lock', icon: 'vpn_lock',
image: '' image: ''
}, },
{ {
id: 7, id: 7,
title: 'Network Infrastructure Support', title: 'Network Infrastructure Support',
description: 'Robust network solutions to ensure connectivity, security, and efficiency, including routers, access points, and switches.', description: 'Robust network solutions to ensure connectivity, security, and efficiency, including routers, access points, and switches.',
challenge: 'A weak network backbone leads to slow speeds, dropped calls, and security holes.', challenge: 'A weak network backbone leads to slow speeds, dropped calls, and security holes.',
approach: 'We design and maintain enterprise-grade networks that handle your data traffic reliably and securely.', approach: 'We design and maintain enterprise-grade networks that handle your data traffic reliably and securely.',
deliverables: [ deliverables: [
'Router & Switch configuration', 'Router & Switch configuration',
'VLAN segmentation', 'VLAN segmentation',
'Wi-Fi optimization', 'Wi-Fi optimization',
'Network monitoring setup', 'Network monitoring setup',
'Cable management' 'Cable management'
], ],
needs: [ needs: [
'Floor plans (for Wi-Fi)', 'Floor plans (for Wi-Fi)',
'ISP details' 'ISP details'
], ],
icon: 'lan', icon: 'lan',
image: '' image: ''
}, },
{ {
id: 8, id: 8,
title: 'Network Attached Storage', title: 'Network Attached Storage',
description: 'Selection, setup, and maintenance of Network Attached Storage solutions to provide scalable and reliable data storage.', description: 'Selection, setup, and maintenance of Network Attached Storage solutions to provide scalable and reliable data storage.',
challenge: 'Data growth requires scalable storage that is accessible yet secure from loss.', challenge: 'Data growth requires scalable storage that is accessible yet secure from loss.',
approach: 'We deploy NAS solutions that centralize your data with redundancy and easy access for your team.', approach: 'We deploy NAS solutions that centralize your data with redundancy and easy access for your team.',
deliverables: [ deliverables: [
'NAS hardware selection & setup', 'NAS hardware selection & setup',
'RAID configuration', 'RAID configuration',
'User permission management', 'User permission management',
'Remote access configuration', 'Remote access configuration',
'Backup integration' 'Backup integration'
], ],
needs: [ needs: [
'Capacity requirements', 'Capacity requirements',
'Access patterns' 'Access patterns'
], ],
icon: 'storage', icon: 'storage',
image: '' image: ''
} }
]; ];
const ServiceModal: React.FC<{ service: typeof services[0] | null; onClose: () => void }> = ({ service, onClose }) => { const ServiceModal: React.FC<{ service: typeof services[0] | null; onClose: () => void }> = ({ service, onClose }) => {
if (!service) return null; if (!service) return null;
// ESC key & Body Scroll Lock // ESC key & Body Scroll Lock
useEffect(() => { useEffect(() => {
const handleEsc = (e: KeyboardEvent) => { const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose(); if (e.key === 'Escape') onClose();
}; };
// Lock scroll on both html and body to prevent background scrolling // Lock scroll on both html and body to prevent background scrolling
const originalHtmlOverflow = document.documentElement.style.overflow; const originalHtmlOverflow = document.documentElement.style.overflow;
const originalBodyOverflow = document.body.style.overflow; const originalBodyOverflow = document.body.style.overflow;
document.documentElement.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden';
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
window.addEventListener('keydown', handleEsc); window.addEventListener('keydown', handleEsc);
return () => { return () => {
window.removeEventListener('keydown', handleEsc); window.removeEventListener('keydown', handleEsc);
document.documentElement.style.overflow = originalHtmlOverflow; document.documentElement.style.overflow = originalHtmlOverflow;
document.body.style.overflow = originalBodyOverflow; document.body.style.overflow = originalBodyOverflow;
}; };
}, [onClose]); }, [onClose]);
return ( return (
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-md" className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-md"
onClick={onClose} onClick={onClose}
> >
<motion.div <motion.div
initial={{ scale: 0.9, opacity: 0 }} initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }} exit={{ scale: 0.9, opacity: 0 }}
className="bg-[#1a1a1a] border border-white/10 rounded-2xl w-full max-w-4xl max-h-[90vh] overflow-y-auto relative shadow-2xl custom-scrollbar" className="bg-[#1a1a1a] border border-white/10 rounded-2xl w-full max-w-4xl max-h-[90vh] overflow-y-auto relative shadow-2xl custom-scrollbar"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> data-lenis-prevent
{/* Close Button - Sticky and distinct */} >
<button {/* Close Button - Sticky and distinct */}
onClick={onClose} <button
className="fixed md:absolute top-6 right-6 p-2 text-white bg-black/60 hover:bg-white/20 backdrop-blur-sm rounded-full transition-all z-50 group border border-white/10 hover:border-white/40 shadow-lg" onClick={onClose}
aria-label="Close modal" className="fixed md:absolute top-6 right-6 p-2 text-white bg-black/60 hover:bg-white/20 backdrop-blur-sm rounded-full transition-all z-50 group border border-white/10 hover:border-white/40 shadow-lg"
> aria-label="Close modal"
<span className="material-symbols-outlined group-hover:scale-110 transition-transform">close</span> >
</button> <span className="material-symbols-outlined group-hover:scale-110 transition-transform">close</span>
</button>
{/* Hero Image in Modal */}
{service.image && ( {/* Hero Image in Modal */}
<div className="w-full h-64 relative"> {service.image && (
<img <div className="w-full h-64 relative">
src={service.image} <img
alt={service.title} src={service.image}
className="w-full h-full object-cover" alt={service.title}
/> className="w-full h-full object-cover"
<div className="absolute inset-0 bg-gradient-to-t from-[#1a1a1a] to-transparent"></div> />
</div> <div className="absolute inset-0 bg-gradient-to-t from-[#1a1a1a] to-transparent"></div>
)} </div>
)}
<div className="p-8 md:p-12 relative">
<div className="flex items-center gap-6 mb-8"> <div className="p-8 md:p-12 relative">
<div className="w-16 h-16 bg-white/5 border border-white/10 rounded-2xl flex items-center justify-center shrink-0"> <div className="flex items-center gap-6 mb-8">
<span className="material-symbols-outlined text-3xl text-white">{service.icon}</span> <div className="w-16 h-16 bg-white/5 border border-white/10 rounded-2xl flex items-center justify-center shrink-0">
</div> <span className="material-symbols-outlined text-3xl text-white">{service.icon}</span>
<div> </div>
<h2 className="text-3xl font-bold text-white mb-2">{service.title}</h2> <div>
<p className="text-gray-400 text-lg">{service.description}</p> <h2 className="text-3xl font-bold text-white mb-2">{service.title}</h2>
</div> <p className="text-gray-400 text-lg">{service.description}</p>
</div> </div>
{/* ... rest of content */} </div>
{/* ... rest of content */}
<div className="grid md:grid-cols-2 gap-12 mb-12">
<div> <div className="grid md:grid-cols-2 gap-12 mb-12">
<h4 className="text-white font-bold uppercase tracking-wider text-xs mb-4 flex items-center gap-2"> <div>
<span className="material-symbols-outlined text-sm">warning</span> The Challenge <h4 className="text-white font-bold uppercase tracking-wider text-xs mb-4 flex items-center gap-2">
</h4> <span className="material-symbols-outlined text-sm">warning</span> The Challenge
<p className="text-gray-300 leading-relaxed text-sm md:text-base">{service.challenge}</p> </h4>
</div> <p className="text-gray-300 leading-relaxed text-sm md:text-base">{service.challenge}</p>
<div> </div>
<h4 className="text-white font-bold uppercase tracking-wider text-xs mb-4 flex items-center gap-2"> <div>
<span className="material-symbols-outlined text-sm">lightbulb</span> Our Approach <h4 className="text-white font-bold uppercase tracking-wider text-xs mb-4 flex items-center gap-2">
</h4> <span className="material-symbols-outlined text-sm">lightbulb</span> Our Approach
<p className="text-gray-300 leading-relaxed text-sm md:text-base">{service.approach}</p> </h4>
</div> <p className="text-gray-300 leading-relaxed text-sm md:text-base">{service.approach}</p>
</div> </div>
</div>
<div className="mb-8">
<h4 className="font-bold text-white mb-6 flex items-center gap-2"> <div className="mb-8">
<span className="material-symbols-outlined">check_circle</span> <h4 className="font-bold text-white mb-6 flex items-center gap-2">
What We Deliver <span className="material-symbols-outlined">check_circle</span>
</h4> What We Deliver
<div className="grid md:grid-cols-2 gap-4"> </h4>
{service.deliverables.map((item, i) => ( <div className="grid md:grid-cols-2 gap-4">
<div key={i} className="flex items-start gap-3 p-3 rounded-xl bg-white/5 border border-white/5 hover:border-white/20 transition-colors"> {service.deliverables.map((item, i) => (
<span className="w-1.5 h-1.5 rounded-full bg-white mt-2 flex-shrink-0 shadow-[0_0_8px_rgba(255,255,255,0.5)]"></span> <div key={i} className="flex items-start gap-3 p-3 rounded-xl bg-white/5 border border-white/5 hover:border-white/20 transition-colors">
<span className="text-gray-300 text-sm">{item}</span> <span className="w-1.5 h-1.5 rounded-full bg-white mt-2 flex-shrink-0 shadow-[0_0_8px_rgba(255,255,255,0.5)]"></span>
</div> <span className="text-gray-300 text-sm">{item}</span>
))} </div>
</div> ))}
</div> </div>
</div>
<div className="pt-8 border-t border-white/10">
<h4 className="font-semibold text-white mb-3 text-sm uppercase tracking-wide opacity-80">What we need from you</h4> <div className="pt-8 border-t border-white/10">
<ul className="text-sm text-gray-400 space-y-2"> <h4 className="font-semibold text-white mb-3 text-sm uppercase tracking-wide opacity-80">What we need from you</h4>
{service.needs.map((item, i) => ( <ul className="text-sm text-gray-400 space-y-2">
<li key={i} className="flex items-center gap-2"> {service.needs.map((item, i) => (
<span className="w-1 h-1 rounded-full bg-white/50"></span> <li key={i} className="flex items-center gap-2">
{item} <span className="w-1 h-1 rounded-full bg-white/50"></span>
</li> {item}
))} </li>
</ul> ))}
</div> </ul>
</div> </div>
</motion.div> </div>
</motion.div> </motion.div>
); </motion.div>
}; );
};
const ServicesPage: React.FC = () => {
const [selectedService, setSelectedService] = useState<typeof services[0] | null>(null); const ServicesPage: React.FC = () => {
const containerRef = React.useRef<HTMLDivElement>(null); const [selectedService, setSelectedService] = useState<typeof services[0] | null>(null);
const { scrollYProgress } = useScroll({ const containerRef = React.useRef<HTMLDivElement>(null);
target: containerRef, const { scrollYProgress } = useScroll({
offset: ["start end", "end center"] target: containerRef,
}); offset: ["start end", "end center"]
});
const heightTransform = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
const heightTransform = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
const [activeTimelineIndex, setActiveTimelineIndex] = useState(0);
const [activeTimelineIndex, setActiveTimelineIndex] = useState(0);
useMotionValueEvent(scrollYProgress, "change", (latest) => {
// Calculate index based on scroll progress (0-1) useMotionValueEvent(scrollYProgress, "change", (latest) => {
// Adjust multiplier to trigger slightly earlier or exact matches // Calculate index based on scroll progress (0-1)
const index = Math.min(Math.floor((latest) * services.length), services.length - 1); // Adjust multiplier to trigger slightly earlier or exact matches
setActiveTimelineIndex(index); const index = Math.min(Math.floor((latest) * services.length), services.length - 1);
}); setActiveTimelineIndex(index);
});
useEffect(() => {
window.scrollTo(0, 0); useEffect(() => {
}, []); window.scrollTo(0, 0);
}, []);
return (
<div className="pt-20 min-h-screen bg-[#0a0a0a] relative overflow-x-hidden"> return (
{/* Gradient for Services Page */} <div className="pt-20 min-h-screen bg-[#0a0a0a] relative overflow-x-hidden">
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.15),rgba(255,255,255,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" /> {/* Gradient for Services Page */}
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.15),rgba(255,255,255,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" />
{/* Hero */}
<section className="py-20 px-6 bg-transparent border-b border-white/5 relative z-10"> {/* Hero */}
<div className="max-w-4xl mx-auto text-center"> <section className="py-20 px-6 bg-transparent border-b border-white/5 relative z-10">
<span className="text-white/60 font-bold tracking-widest uppercase text-sm mb-3 block">Expertise</span> <div className="max-w-4xl mx-auto text-center">
<h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-white"> <span className="text-white/60 font-bold tracking-widest uppercase text-sm mb-3 block">Expertise</span>
Complete IT solutions for <br /><span className="text-gray-500">your business</span> <h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-white">
</h1> Complete IT solutions for <br /><span className="text-gray-500">your business</span>
<p className="text-xl text-gray-400 max-w-2xl mx-auto"> </h1>
From desktop support to enterprise infrastructure, we provide the technology foundation your business needs to thrive. <p className="text-xl text-gray-400 max-w-2xl mx-auto">
</p> From desktop support to enterprise infrastructure, we provide the technology foundation your business needs to thrive.
</div> </p>
</section> </div>
</section>
{/* Timeline Section */}
<section ref={containerRef} className="py-24 px-6 relative overflow-hidden bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]"> {/* Timeline Section */}
<div className="max-w-7xl mx-auto relative z-10"> <section ref={containerRef} className="py-24 px-6 relative overflow-hidden bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
{/* Central Timeline Line */} <div className="max-w-7xl mx-auto relative z-10">
<div className="absolute left-4 md:left-1/2 md:-ml-[0.5px] top-0 bottom-0 w-px bg-white/5 -translate-x-1/2 md:translate-x-0"></div> {/* Central Timeline Line */}
<div className="absolute left-4 md:left-1/2 md:-ml-[0.5px] top-0 bottom-0 w-px bg-white/5 -translate-x-1/2 md:translate-x-0"></div>
{/* Active Timeline Line (Scroll Driven) */}
<motion.div {/* Active Timeline Line (Scroll Driven) */}
style={{ height: heightTransform }} <motion.div
className="absolute left-4 md:left-1/2 md:-ml-[0.5px] top-0 w-px bg-white origin-top shadow-[0_0_40px_2px_rgba(255,255,255,1)] drop-shadow-[0_0_10px_rgba(255,255,255,1)] -translate-x-1/2 md:translate-x-0" style={{ height: heightTransform }}
></motion.div> className="absolute left-4 md:left-1/2 md:-ml-[0.5px] top-0 w-px bg-white origin-top shadow-[0_0_40px_2px_rgba(255,255,255,1)] drop-shadow-[0_0_10px_rgba(255,255,255,1)] -translate-x-1/2 md:translate-x-0"
></motion.div>
<div className="space-y-64 pb-64">
{services.map((service, index) => ( <div className="space-y-64 pb-64">
<div {services.map((service, index) => (
key={service.id} <div
className={`relative flex flex-col md:flex-row gap-8 md:gap-0 ${index % 2 === 0 ? 'md:flex-row' : 'md:flex-row-reverse'}`} key={service.id}
> className={`relative flex flex-col md:flex-row gap-8 md:gap-0 ${index % 2 === 0 ? 'md:flex-row' : 'md:flex-row-reverse'}`}
{/* Timeline Dot */} >
<motion.div {/* Timeline Dot */}
animate={index <= activeTimelineIndex ? { <motion.div
scale: 1.2, animate={index <= activeTimelineIndex ? {
opacity: 1, scale: 1.2,
backgroundColor: "#ffffff", opacity: 1,
borderColor: "#ffffff", backgroundColor: "#ffffff",
boxShadow: "0 0 30px rgba(255,255,255,1)" borderColor: "#ffffff",
} : { boxShadow: "0 0 30px rgba(255,255,255,1)"
scale: 0.5, } : {
opacity: 0.2, scale: 0.5,
backgroundColor: "#0a0a0a", opacity: 0.2,
borderColor: "rgba(255,255,255,0.1)", backgroundColor: "#0a0a0a",
boxShadow: "none" borderColor: "rgba(255,255,255,0.1)",
}} boxShadow: "none"
viewport={{ margin: "-50% 0px -50% 0px" }} }}
transition={{ duration: 0.3 }} viewport={{ margin: "-50% 0px -50% 0px" }}
className="absolute left-4 md:left-1/2 md:-ml-[6px] -translate-x-1/2 md:translate-x-0 w-3 h-3 rounded-full border border-white/20 z-20 top-1/2 -translate-y-1/2 shrink-0" transition={{ duration: 0.3 }}
></motion.div> className="absolute left-4 md:left-1/2 md:-ml-[6px] -translate-x-1/2 md:translate-x-0 w-3 h-3 rounded-full border border-white/20 z-20 top-1/2 -translate-y-1/2 shrink-0"
></motion.div>
{/* Content Card */}
<motion.div {/* Content Card */}
initial={{ opacity: 0, scale: 0.9, filter: "blur(10px)" }} <motion.div
whileInView={{ opacity: 1, scale: 1, filter: "blur(0px)" }} initial={{ opacity: 0, scale: 0.9, filter: "blur(10px)" }}
viewport={{ once: true, margin: "-100px" }} whileInView={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
transition={{ duration: 0.5, ease: "easeOut" }} viewport={{ once: true, margin: "-100px" }}
className={`md:w-1/2 ${index % 2 === 0 ? 'md:pr-24 pl-12' : 'md:pl-24 pl-12'}`} transition={{ duration: 0.5, ease: "easeOut" }}
> className={`md:w-1/2 ${index % 2 === 0 ? 'md:pr-24 pl-12' : 'md:pl-24 pl-12'}`}
<div >
onClick={() => setSelectedService(service)} <div
className="group cursor-pointer bg-white/5 backdrop-blur-sm rounded-2xl p-10 border border-white/10 hover:border-white/30 hover:bg-white/10 transition-all duration-300 relative overflow-hidden" onClick={() => setSelectedService(service)}
> className="group cursor-pointer bg-white/5 backdrop-blur-sm rounded-2xl p-10 border border-white/10 hover:border-white/30 hover:bg-white/10 transition-all duration-300 relative overflow-hidden"
<div className="w-14 h-14 bg-white/5 rounded-xl flex items-center justify-center mb-8 border border-white/10 group-hover:scale-110 transition-transform duration-500"> >
<span className="material-symbols-outlined text-3xl text-white">{service.icon}</span> <div className="w-14 h-14 bg-white/5 rounded-xl flex items-center justify-center mb-8 border border-white/10 group-hover:scale-110 transition-transform duration-500">
</div> <span className="material-symbols-outlined text-3xl text-white">{service.icon}</span>
</div>
<h3 className="text-3xl font-bold text-white mb-4">{service.title}</h3>
<p className="text-gray-400 mb-8 line-clamp-3 text-lg leading-relaxed">{service.description}</p> <h3 className="text-3xl font-bold text-white mb-4">{service.title}</h3>
<p className="text-gray-400 mb-8 line-clamp-3 text-lg leading-relaxed">{service.description}</p>
<div className="flex items-center text-sm font-bold text-white uppercase tracking-wider group-hover:gap-2 transition-all">
See Details <span className="material-symbols-outlined text-sm ml-1">arrow_forward</span> <div className="flex items-center text-sm font-bold text-white uppercase tracking-wider group-hover:gap-2 transition-all">
</div> See Details <span className="material-symbols-outlined text-sm ml-1">arrow_forward</span>
</div> </div>
</motion.div> </div>
</motion.div>
{/* Spacer for the other side */}
<div className="md:w-1/2"></div> {/* Spacer for the other side */}
</div> <div className="md:w-1/2"></div>
))} </div>
</div> ))}
</div> </div>
</section> </div>
</section>
<Contact />
<Contact />
{/* Modal */}
<AnimatePresence> {/* Modal */}
{selectedService && ( <AnimatePresence>
<ServiceModal service={selectedService} onClose={() => setSelectedService(null)} /> {selectedService && (
)} <ServiceModal service={selectedService} onClose={() => setSelectedService(null)} />
</AnimatePresence> )}
</div> </AnimatePresence>
); </div>
}; );
};
export default ServicesPage;
export default ServicesPage;