This commit is contained in:
2025-09-30 23:10:13 +02:00
parent a646542d8f
commit 9938c1f9e2
27 changed files with 2158 additions and 498 deletions

View File

@@ -0,0 +1,29 @@
import { useCountUp, parseNumberFromString, formatWithOriginalString } from '@/hooks/use-countup';
interface CountUpNumberProps {
value: string;
duration?: number;
className?: string;
}
const CountUpNumber = ({ value, duration = 2000, className = '' }: CountUpNumberProps) => {
const numericValue = parseNumberFromString(value);
const decimals = value.includes('.') ? value.split('.')[1].match(/\d+/)?.[0]?.length || 0 : 0;
const { count, elementRef } = useCountUp({
end: numericValue,
duration,
decimals,
startOnInView: true
});
const formattedValue = formatWithOriginalString(value, count);
return (
<span ref={elementRef} className={className}>
{formattedValue}
</span>
);
};
export default CountUpNumber;

View File

@@ -1,10 +1,7 @@
import { Link } from 'react-router-dom';
import { MapPin, Phone, Mail, ArrowUp } from 'lucide-react';
import { MapPin, Phone, Mail } from 'lucide-react';
const Footer = () => {
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const quickLinks = [
{ name: 'Services', path: '/services' },
@@ -23,17 +20,8 @@ const Footer = () => {
];
return (
<footer className="bg-background-deep border-t border-border relative">
{/* Back to top button */}
<button
onClick={scrollToTop}
className="absolute -top-6 left-1/2 transform -translate-x-1/2 w-12 h-12 bg-neon rounded-full flex items-center justify-center text-neon-foreground hover:shadow-neon transition-all duration-300 hover:-translate-y-1"
aria-label="Back to top"
>
<ArrowUp className="w-5 h-5" />
</button>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-16 pb-8">
<footer className="bg-background-deep border-t border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12">
{/* Company info */}
<div className="lg:col-span-2">
@@ -45,9 +33,9 @@ const Footer = () => {
Bay Area Affiliates, Inc.
</span>
</div>
<p className="text-foreground-muted mb-6 leading-relaxed max-w-md">
Top-notch Computer & Networking solutions for the Coastal Bend.
Top-notch Computer & Networking solutions for the Coastal Bend.
We design, run and protect your IT so you can focus on growth.
</p>
@@ -111,7 +99,7 @@ const Footer = () => {
<div className="text-foreground-muted text-sm mb-4 sm:mb-0">
© 2024 Bay Area Affiliates, Inc. All rights reserved.
</div>
<div className="flex items-center space-x-6 text-sm">
<Link to="/privacy" className="text-foreground-muted hover:text-neon transition-colors">
Privacy Policy

View File

@@ -1,30 +1,21 @@
import { useState, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Menu, X, ChevronUp } from 'lucide-react';
import { Menu, X } from 'lucide-react';
const Navigation = () => {
const [isOpen, setIsOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const [showScrollUp, setShowScrollUp] = useState(false);
const location = useLocation();
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 50);
setShowScrollUp(window.scrollY > 300);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
const navItems = [
{ name: 'Home', path: '/' },
{ name: 'Services', path: '/services' },
@@ -36,18 +27,21 @@ const Navigation = () => {
const isActive = (path: string) => location.pathname === path;
return (
<nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
isScrolled ? 'bg-background/90 backdrop-blur-md border-b border-border' : 'bg-transparent'
}`}>
<nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${isScrolled
? 'bg-white/5 backdrop-blur-2xl border-b border-white/20 shadow-2xl shadow-black/20'
: 'bg-transparent'
}`}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo */}
<Link to="/" className="flex items-center space-x-3">
<img
src="/logo_bayarea.svg"
alt="Bay Area Affiliates Logo"
className="w-8 h-8 opacity-90 hover:opacity-100 transition-opacity duration-300"
/>
<div className="w-12 h-12 flex items-center justify-center overflow-visible">
<img
src="/logo_bayarea.svg"
alt="Bay Area Affiliates Logo"
className="w-10 h-10 opacity-90 hover:opacity-100 transition-opacity duration-300"
/>
</div>
<span className="font-heading font-bold text-lg text-white">
Bay Area Affiliates
</span>
@@ -59,11 +53,10 @@ const Navigation = () => {
<Link
key={item.name}
to={item.path}
className={`font-medium transition-colors duration-200 ${
isActive(item.path)
? 'text-neon'
: 'text-white hover:text-neon'
}`}
className={`font-medium transition-colors duration-200 ${isActive(item.path)
? 'text-neon'
: 'text-white hover:text-neon'
}`}
>
{item.name}
</Link>
@@ -88,18 +81,17 @@ const Navigation = () => {
{/* Mobile Navigation */}
{isOpen && (
<div className="md:hidden bg-background/95 backdrop-blur-md border-t border-border">
<div className="md:hidden bg-white/5 backdrop-blur-2xl border-t border-white/20">
<div className="px-2 pt-2 pb-3 space-y-1">
{navItems.map((item) => (
<Link
key={item.name}
to={item.path}
onClick={() => setIsOpen(false)}
className={`block px-3 py-2 rounded-md text-base font-medium transition-colors ${
isActive(item.path)
? 'text-neon bg-neon/10'
: 'text-white hover:text-neon hover:bg-neon/5'
}`}
className={`block px-3 py-2 rounded-md text-base font-medium transition-colors ${isActive(item.path)
? 'text-neon bg-neon/10'
: 'text-white hover:text-neon hover:bg-neon/5'
}`}
>
{item.name}
</Link>
@@ -115,17 +107,6 @@ const Navigation = () => {
</div>
)}
</div>
{/* Scroll to top button */}
{showScrollUp && (
<button
onClick={scrollToTop}
className="fixed bottom-8 right-8 z-50 w-12 h-12 bg-neon text-neon-foreground rounded-full shadow-lg hover:shadow-neon transition-all duration-300 flex items-center justify-center group hover:scale-110"
aria-label="Scroll to top"
>
<ChevronUp className="w-6 h-6 transition-transform group-hover:-translate-y-0.5" />
</button>
)}
</nav>
);
};

View File

@@ -0,0 +1,19 @@
import { useSEO, SEOProps } from '@/hooks/useSEO';
/**
* Reusable SEO component for managing page metadata
* Use this component at the top of each page component
*
* @example
* <SEOHead
* title="Managed IT Services Corpus Christi | Bay Area Affiliates"
* description="Secure, tailored IT support for local businesses"
* canonical="https://bayarea-cc.com/"
* />
*/
export const SEOHead = (props: SEOProps) => {
useSEO(props);
return null; // This component only manages side effects
};
export default SEOHead;

View File

@@ -1,44 +1,46 @@
import { useEffect, useRef, ReactNode } from 'react';
import { ReactNode, useLayoutEffect, useRef } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
interface ScrollRevealProps {
gsap.registerPlugin(ScrollTrigger);
type ScrollRevealProps = {
children: ReactNode;
delay?: number;
className?: string;
}
};
const ScrollReveal = ({ children, delay = 0, className = '' }: ScrollRevealProps) => {
const elementRef = useRef<HTMLDivElement>(null);
useEffect(() => {
useLayoutEffect(() => {
const element = elementRef.current;
if (!element) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setTimeout(() => {
element.classList.add('revealed');
}, delay);
}
});
},
{
threshold: 0.1,
rootMargin: '0px 0px -50px 0px',
}
);
const ctx = gsap.context(() => {
gsap.fromTo(
element,
{ autoAlpha: 0, y: 32 },
{
autoAlpha: 1,
y: 0,
duration: 0.8,
ease: 'power3.out',
delay: delay / 1000,
scrollTrigger: {
trigger: element,
start: 'top 85%',
once: true,
},
}
);
}, element);
observer.observe(element);
return () => observer.unobserve(element);
return () => ctx.revert();
}, [delay]);
return (
<div
ref={elementRef}
className={`scroll-reveal ${className}`}
>
<div ref={elementRef} className={className}>
{children}
</div>
);

View File

@@ -24,27 +24,21 @@ const HeroSection = () => {
<div className="relative h-full flex items-center justify-center overflow-hidden">
{/* Background image with parallax */}
<div className="absolute inset-0 overflow-hidden">
<img
<img
ref={imageRef}
src="/serverroom.png"
alt="Modern IT infrastructure with network connections"
src="/serverroom.png"
alt="Modern IT infrastructure with network connections"
className="w-full h-[110%] object-cover will-change-transform"
style={{ transform: 'translateY(0px) scale(1.1)' }}
/>
{/* Dark overlay */}
<div className="absolute inset-0 bg-black/35"></div>
</div>
{/* Main content */}
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<div className="max-w-4xl mx-auto">
{/* Logo */}
<div className="flex justify-center mb-8">
<img
src="/logo_bayarea.svg"
alt="Bay Area Affiliates Logo"
className="w-20 h-20 sm:w-24 sm:h-24 opacity-95 hover:opacity-100 transition-opacity duration-300 drop-shadow-[0_0_10px_rgba(255,255,255,0.3)]"
/>
</div>
{/* Badge */}
<div className="inline-flex items-center px-4 py-2 rounded-full bg-neon/20 border border-neon/40 text-neon text-sm font-medium mb-8 drop-shadow-[0_0_10px_rgba(51,102,255,0.5)]">
<span className="w-2 h-2 bg-neon rounded-full mr-2 animate-glow-pulse"></span>
@@ -59,7 +53,7 @@ const HeroSection = () => {
{/* Subheadline */}
<p className="text-xl sm:text-2xl text-white/95 mb-12 max-w-3xl mx-auto leading-relaxed drop-shadow-[0_0_15px_rgba(255,255,255,0.2)]">
From fast devices to secure remote access and resilient networks we design,
From fast devices to secure remote access and resilient networks we design,
run and protect your tech so you can focus on growth.
</p>
@@ -72,24 +66,13 @@ const HeroSection = () => {
<span>Get in touch</span>
<ArrowRight className="w-5 h-5 transition-transform group-hover:translate-x-1" />
</Link>
<button className="btn-ghost group flex items-center space-x-2">
<Play className="w-5 h-5 transition-transform group-hover:scale-110" />
<span>See our system</span>
</button>
</div>
{/* Trust indicators */}
<div className="mt-16 pt-8 border-t border-white/30">
<p className="text-sm text-white/90 mb-6 drop-shadow-[0_0_10px_rgba(255,255,255,0.2)]">Trusted by businesses across Corpus Christi</p>
<div className="flex flex-wrap justify-center items-center gap-8">
{['Healthcare', 'Manufacturing', 'Professional Services'].map((industry) => (
<span key={industry} className="text-base font-bold text-white drop-shadow-[0_0_20px_rgba(255,255,255,0.6)] drop-shadow-[0_0_40px_rgba(255,255,255,0.3)]">
{industry}
</span>
))}
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import { MapPin, Star, Users } from 'lucide-react';
import ScrollReveal from '../ScrollReveal';
import CountUpNumber from '../CountUpNumber';
const ProofSection = () => {
const stats = [
@@ -29,15 +30,15 @@ const ProofSection = () => {
<MapPin className="w-4 h-4 mr-2" />
Proudly serving the Coastal Bend
</div>
<h2 className="font-heading font-bold text-4xl sm:text-5xl text-foreground mb-6">
Local expertise for{' '}
<span className="text-neon">Corpus Christi</span>{' '}
and surrounding communities
</h2>
<p className="text-xl text-foreground-muted max-w-3xl mx-auto">
We understand the unique challenges of businesses in our region and provide
We understand the unique challenges of businesses in our region and provide
tailored solutions that work in the real world.
</p>
</div>
@@ -49,7 +50,11 @@ const ProofSection = () => {
{stats.map((stat, index) => (
<div key={stat.label} className="text-center">
<div className="font-heading font-bold text-4xl lg:text-5xl text-neon mb-2">
{stat.value}
<CountUpNumber
value={stat.value}
duration={2000 + index * 200}
className="inline-block"
/>
</div>
<div className="text-foreground-muted text-sm lg:text-base">
{stat.label}
@@ -70,11 +75,11 @@ const ProofSection = () => {
<Star key={i} className="w-5 h-5 text-neon fill-current" />
))}
</div>
<blockquote className="text-lg lg:text-xl text-foreground leading-relaxed mb-6">
"{testimonial.quote}"
</blockquote>
<div className="flex items-center">
<div className="w-12 h-12 bg-neon/20 rounded-full flex items-center justify-center mr-4">
<Users className="w-6 h-6 text-neon" />
@@ -97,10 +102,10 @@ const ProofSection = () => {
<h3 className="font-heading font-semibold text-xl text-foreground mb-6">
Serving businesses throughout the Coastal Bend
</h3>
<div className="flex flex-wrap justify-center items-center gap-6 text-foreground-muted">
{[
'Corpus Christi', 'Portland', 'Ingleside', 'Aransas Pass',
'Corpus Christi', 'Portland', 'Ingleside', 'Aransas Pass',
'Rockport', 'Fulton', 'Sinton', 'Mathis'
].map((city) => (
<span key={city} className="flex items-center text-sm">