SEO/AEO
This commit is contained in:
29
src/components/CountUpNumber.tsx
Normal file
29
src/components/CountUpNumber.tsx
Normal 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;
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
19
src/components/SEOHead.tsx
Normal file
19
src/components/SEOHead.tsx
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user