255 lines
16 KiB
TypeScript
255 lines
16 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { motion, useInView, useSpring, useTransform, useScroll, useMotionValueEvent } from 'framer-motion';
|
|
import Contact from '../../components/Contact';
|
|
import SEO from '../../components/SEO';
|
|
|
|
const Counter = ({ value }: { value: number }) => {
|
|
const ref = useRef(null);
|
|
const isInView = useInView(ref, { once: true, margin: "-20%" });
|
|
const spring = useSpring(0, { mass: 3, stiffness: 75, damping: 30 });
|
|
const display = useTransform(spring, (current) =>
|
|
value % 1 !== 0 ? current.toFixed(1) : Math.round(current).toLocaleString()
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (isInView) {
|
|
spring.set(value);
|
|
}
|
|
}, [isInView, value, spring]);
|
|
|
|
return <motion.span ref={ref}>{display}</motion.span>;
|
|
};
|
|
|
|
const AboutPage: React.FC = () => {
|
|
const timelineRef = useRef<HTMLDivElement>(null);
|
|
const [activeTimelineIndex, setActiveTimelineIndex] = useState(0);
|
|
|
|
const { scrollYProgress } = useScroll({
|
|
target: timelineRef,
|
|
offset: ["start end", "end center"]
|
|
});
|
|
|
|
const heightTransform = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
|
|
|
|
useMotionValueEvent(scrollYProgress, "change", (latest) => {
|
|
// 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
|
|
setActiveTimelineIndex(Math.min(index, 3));
|
|
});
|
|
|
|
useEffect(() => {
|
|
window.scrollTo(0, 0);
|
|
}, []);
|
|
|
|
const values = [
|
|
{
|
|
title: 'Security-First',
|
|
desc: 'Every solution we implement prioritizes your data security and business continuity.',
|
|
icon: 'security'
|
|
},
|
|
{
|
|
title: 'Reliability',
|
|
desc: 'We build systems that work consistently, so you can depend on your technology.',
|
|
icon: 'verified'
|
|
},
|
|
{
|
|
title: 'Clarity',
|
|
desc: 'No tech jargon or hidden fees. We explain what we do and why it matters.',
|
|
icon: 'visibility'
|
|
}
|
|
];
|
|
|
|
const timeline = [
|
|
{ year: '1996', 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: '2020', title: 'Remote Work Transformation', desc: 'Helped local businesses strengthen remote access, security, and day-to-day support during a disruptive period.' },
|
|
{ year: '2024', title: 'Leading the Coastal Bend', desc: 'Now supporting 30+ local businesses with practical, reliable IT infrastructure.' },
|
|
];
|
|
|
|
return (
|
|
<>
|
|
<SEO
|
|
title="About Bay Area Affiliates | Local IT Support in Corpus Christi"
|
|
description="Learn about Bay Area Affiliates, a local IT partner serving Corpus Christi and the Coastal Bend with practical support, reliable service, and over 30 years of experience."
|
|
keywords={['about Bay Area Affiliates', 'Corpus Christi IT company', 'local IT support Coastal Bend']}
|
|
canonicalUrl="https://bayareait.services/about"
|
|
/>
|
|
<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" />
|
|
|
|
{/* Hero Section */}
|
|
<section className="relative py-20 px-6 overflow-hidden">
|
|
<div className="max-w-7xl mx-auto text-center relative z-10">
|
|
<motion.h1
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
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>
|
|
</motion.h1>
|
|
<motion.p
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.1 }}
|
|
className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto leading-relaxed"
|
|
>
|
|
Since 1996, we've been helping businesses in Corpus Christi and surrounding communities build reliable, secure technology foundations that keep work moving.
|
|
</motion.p>
|
|
</div>
|
|
</section>
|
|
|
|
{/* 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))]">
|
|
<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">
|
|
<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">
|
|
<p>
|
|
Bay Area Affiliates was founded with a simple belief: local businesses deserve dependable technology support without enterprise complexity, vague communication, or reactive chaos.
|
|
</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.
|
|
</p>
|
|
<p>
|
|
Today, we're proud to support 30+ local businesses across the region. Our team combines deep technical experience with real-world business judgment to deliver IT support that is clear, practical and reliable.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Stats */}
|
|
<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="max-w-7xl mx-auto grid grid-cols-2 md:grid-cols-4 gap-8 text-center relative z-10">
|
|
{[
|
|
{ label: 'Businesses served', value: 30, suffix: '+' },
|
|
{ label: 'Uptime target', value: 99.9, suffix: '%' },
|
|
{ label: 'Years of service', value: 30, suffix: '+' },
|
|
{ label: 'Local Corpus Christi support', value: null },
|
|
].map((stat, index) => (
|
|
<div key={index} className="p-4">
|
|
{stat.value !== null ? (
|
|
<>
|
|
<div className="text-4xl md:text-5xl font-bold mb-2 flex justify-center items-center gap-1">
|
|
{stat.prefix && <span>{stat.prefix}</span>}
|
|
<Counter value={stat.value} />
|
|
{stat.suffix && <span>{stat.suffix}</span>}
|
|
</div>
|
|
<div className="text-gray-400 font-medium">{stat.label}</div>
|
|
</>
|
|
) : (
|
|
<div className="text-4xl md:text-5xl font-bold mb-2">{stat.label}</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
{/* Values */}
|
|
<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 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="text-center mb-16">
|
|
<h2 className="font-display text-3xl font-bold mb-4 text-gray-900 dark:text-white">Our Values</h2>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
{values.map((value, index) => (
|
|
<motion.div
|
|
key={index}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: index * 0.1 }}
|
|
whileHover={{ y: -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">
|
|
<span className="material-symbols-outlined text-2xl">{value.icon}</span>
|
|
</div>
|
|
<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">
|
|
{value.desc}
|
|
</p>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Timeline */}
|
|
<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="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>
|
|
|
|
<div className="space-y-12 relative">
|
|
{/* 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>
|
|
|
|
{/* Light Beam Line */}
|
|
<motion.div
|
|
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"
|
|
></motion.div>
|
|
|
|
{timeline.map((item, index) => (
|
|
<motion.div
|
|
key={index}
|
|
initial={{ opacity: 0, x: index % 2 === 0 ? -50 : 50 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true }}
|
|
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'}`}
|
|
>
|
|
<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">
|
|
{item.year}
|
|
</span>
|
|
<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>
|
|
</div>
|
|
|
|
{/* 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">
|
|
<motion.div
|
|
className="w-4 h-4 rounded-full border-2 transition-colors duration-500"
|
|
animate={index <= activeTimelineIndex ? {
|
|
backgroundColor: "#000000", // Will be white in dark mode via class if needed, checking global styles
|
|
scale: 1.5,
|
|
borderColor: "#000000",
|
|
boxShadow: "0 0 20px rgba(0,0,0,0.5)"
|
|
} : {
|
|
backgroundColor: "transparent",
|
|
scale: 1,
|
|
borderColor: "rgba(156, 163, 175, 0.5)", // gray-400
|
|
boxShadow: "none"
|
|
}}
|
|
style={{
|
|
// 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,
|
|
// but better to rely on simpler style.
|
|
// 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.
|
|
// 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'}`} />
|
|
</motion.div>
|
|
</div>
|
|
<div className="md:w-1/2"></div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<Contact />
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
|
|
export default AboutPage;
|