Pushing project code

This commit is contained in:
Timo Knuth
2026-01-16 18:05:03 +01:00
parent 1a85f0eb2d
commit a3cd5f30dd
29 changed files with 3491 additions and 445 deletions

View File

@@ -8,24 +8,24 @@ gsap.registerPlugin(ScrollTrigger);
const posts = [
{
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuARalmRkuoZMBAbavGQgx4a-JhLgXBJ6JSD0U4vycdwaGGV3d-ffUFrdbx2lIbKrYCmS100i7VJ0w5cDHITXYV6w1-pSUPHKL7Jik__TWOIYOnq_4ND5ri7l8SQoaJdjJK9jhYvtxdxrZm6j8t8BNAjvPTaUdUDo4C7QVqcx1KbGvup6cpF8vY1LJ82S_5OMAZ6JgH0rK5bvWpqD3WqPhtqJCUB6d_1gUvluKjotwnNQ03t1dSYV8HOtRrLE83j6i_wgL4GZ0XTsMZb',
date: 'Oct 12, 2024',
category: 'Cybersecurity',
title: 'The Hidden Risks of Remote Work',
excerpt: 'As remote work becomes permanent, new vulnerabilities emerge. Learn how to secure your distributed workforce effectively.'
date: 'Jan 10, 2026',
category: 'Performance',
title: 'Upgrade Your HDD to SSD for Enhanced Performance',
excerpt: 'In today\'s fast-paced digital world, the performance of your computer can make a significant difference in productivity...'
},
{
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCz5lTYjY4RNXubQlrA-BtLIGR3nUY8ULkD9omwT5FShfdMrbMgS5dDCyfN3xiB5WC7T3vjNvyvVbvnD0G1zBpbNTjfOYyhmAEfno7Hf5W1sm-KYRXYrLGQq-c6TkLgEf0i9JGNvuFZ6edcenr2o39dCzIPXcp_z9XWOIzp7kBX2EydNPLJoRofVYuSTmEA1y0_xh4sdiRy1PykRASGLhKfN19_XLNuwyTBVKYISY7cHc-An69eZpAfhrvngu3E47rU6KuQS0k3QXBZ',
date: 'Sep 28, 2024',
category: 'Cloud Infrastructure',
title: 'Migrating to the Cloud: A Step-by-Step Guide',
excerpt: 'Thinking about moving your data? Here is a comprehensive checklist to ensure a smooth and secure transition.'
date: 'Jan 5, 2026',
category: 'Security',
title: 'Secure Your Corporate Network Access with WireGuard VPN',
excerpt: 'The safest way to access your corporate network remotely is through a secure VPN connection...'
},
{
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCl5iOhTsCqcHnho89DkoLh0DYeuvef0pdp8k26NKzcAq7YPvWbAYARg9mCIvqGTxQGradp8zvscuuibskpz4W_nEzQQO1z7lgwKJ1Xxiw_yQOyXMLfoRNLTHXzqFUH8Q5daCAfYTb7Zl3sFjB7k8i44D6TGolzqrN05Db27Abf2TWDDzHpVSrNml4zddvxholHFxMzqDeSzQ5p77SLDSFNaYBZGR2lEdN2V9O0GzMqxbOjFmBGMW48nlrEDLDzYGv_gWI3RSqNqBl-',
date: 'Sep 15, 2024',
category: 'Innovation',
title: 'AI in Business: Beyond the Hype',
excerpt: 'Artificial Intelligence is transforming industries. Discover practical applications that can drive efficiency in your business today.'
date: 'Dec 28, 2025',
category: 'Infrastructure',
title: 'Virtualizing Windows Machines: Future-Proof Your Corporate Network',
excerpt: 'In October 2025, Microsoft will end support for Windows 10. Learn how virtualization can help you prepare...'
}
];
@@ -38,7 +38,7 @@ const Blog: React.FC = () => {
const ctx = gsap.context(() => {
imagesRef.current.forEach((imgWrapper) => {
if (!imgWrapper) return;
gsap.to(imgWrapper, {
yPercent: 30,
ease: "none",
@@ -56,25 +56,25 @@ const Blog: React.FC = () => {
}, []);
return (
<motion.section
<motion.section
ref={containerRef}
id="blog"
id="blog"
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8, ease: "easeOut" }}
className="py-24 bg-background-light dark:bg-background-dark border-t border-gray-200 dark:border-white/10"
className="py-24 bg-background-light dark:bg-background-dark border-t border-gray-200 dark:border-white/10 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 px-6">
<div className="flex justify-between items-end mb-12">
<div>
<span className="text-xs font-semibold uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2 block">Latest Insights</span>
<h2 className="font-display text-3xl md:text-4xl text-gray-900 dark:text-white">
Knowledge <span className="text-gray-400 dark:text-gray-600">base.</span>
Stay updated <span className="text-gray-400 dark:text-gray-600">with our latest news and articles.</span>
</h2>
</div>
<motion.a
href="#"
<motion.a
href="#"
className="hidden md:inline-flex items-center text-sm font-medium text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
whileHover={{ x: 5 }}
>
@@ -84,7 +84,7 @@ const Blog: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{posts.map((post, i) => (
<motion.article
<motion.article
key={i}
initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }}
@@ -94,13 +94,13 @@ const Blog: React.FC = () => {
className="group cursor-pointer"
>
<div className="h-64 rounded-xl overflow-hidden mb-6 relative shadow-lg">
<div
ref={el => { if(el) imagesRef.current.push(el); }}
<div
ref={el => { if (el) imagesRef.current.push(el); }}
className="w-full h-[140%] -mt-[20%]"
>
<img
src={post.image}
alt={post.title}
<img
src={post.image}
alt={post.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
/>
</div>

74
components/CTA.tsx Normal file
View File

@@ -0,0 +1,74 @@
import React from 'react';
import { motion } from 'framer-motion';
import { Link } from 'react-router-dom';
const CTA: React.FC = () => {
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))]">
<div className="max-w-4xl mx-auto text-center">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
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>
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
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.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
>
<Link
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"
>
Book a 20-minute assessment
</Link>
<Link
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"
>
Send a message
</Link>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.3 }}
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">
<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>
</div>
<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>
<p className="text-gray-600 dark:text-gray-400 text-sm">Transparent monthly pricing based on devices and services needed.</p>
</div>
<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>
<p className="text-gray-600 dark:text-gray-400 text-sm">24/7 monitoring, helpdesk, proactive maintenance, and SLA guarantees.</p>
</div>
</motion.div>
</div>
</section>
);
};
export default CTA;

View File

@@ -3,8 +3,8 @@ import { motion } from 'framer-motion';
const Contact: React.FC = () => {
return (
<motion.section
id="contact"
<motion.section
id="contact"
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
@@ -12,7 +12,7 @@ const Contact: React.FC = () => {
className="py-24 bg-white dark:bg-[#0f0f0f] border-t border-gray-100 dark:border-white/5"
>
<div className="max-w-3xl mx-auto px-6">
<motion.div
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
@@ -22,11 +22,11 @@ const Contact: React.FC = () => {
Get in Touch
</h2>
<p className="text-gray-600 dark:text-gray-400 text-lg">
Ready to elevate your IT infrastructure? Fill out the form below and we'll get back to you shortly.
We're here to help you with all your IT needs.
</p>
</motion.div>
<motion.form
<motion.form
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
@@ -35,41 +35,68 @@ const Contact: React.FC = () => {
>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name</label>
<motion.input
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name *</label>
<motion.input
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
transition={{ duration: 0.2 }}
type="text"
id="name"
placeholder="John Doe"
type="text"
id="name"
placeholder="Your Name"
required
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Email</label>
<motion.input
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Email *</label>
<motion.input
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
transition={{ duration: 0.2 }}
type="email"
id="email"
placeholder="john@company.com"
type="email"
id="email"
placeholder="Your Email"
required
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Phone (optional)</label>
<motion.input
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
transition={{ duration: 0.2 }}
type="tel"
id="phone"
placeholder="Your Phone Number"
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
/>
</div>
<div>
<label htmlFor="company" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Company (optional)</label>
<motion.input
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
transition={{ duration: 0.2 }}
type="text"
id="company"
placeholder="Your Company Name"
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
/>
</div>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Message</label>
<motion.textarea
<label htmlFor="message" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Message *</label>
<motion.textarea
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
transition={{ duration: 0.2 }}
id="message"
placeholder="Tell us about your project..."
id="message"
placeholder="Your Message"
required
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all h-32 resize-none"
></motion.textarea>
</div>
<div className="text-center">
<motion.button
type="submit"
<motion.button
type="submit"
whileHover={{ scale: 1.05, backgroundColor: "#3b82f6", color: "#ffffff", border: "none" }}
whileTap={{ scale: 0.95 }}
className="px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium transition-all duration-300 w-full md:w-auto shadow-lg"

28
components/Counter.tsx Normal file
View File

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

View File

@@ -1,82 +0,0 @@
import React from 'react';
import { motion } from 'framer-motion';
const Features: React.FC = () => {
const features = [
{
icon: 'verified_user',
title: 'Certified Experts',
desc: 'Our team holds top-tier certifications from Microsoft, Cisco, and AWS, ensuring you get world-class expertise.',
color: 'blue'
},
{
icon: 'rocket_launch',
title: 'Rapid Response',
desc: 'Time is money. We guarantee a 15-minute initial response time for critical issues to keep you moving.',
color: 'purple'
},
{
icon: 'handshake',
title: 'Dedicated Partnership',
desc: "We don't just fix computers; we align IT strategy with your business goals for long-term success.",
color: 'emerald'
}
];
const getColorClasses = (color: string) => {
switch(color) {
case 'blue': return 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400';
case 'purple': return 'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400';
case 'emerald': return 'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-400';
default: return 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400';
}
};
return (
<motion.section
id="features"
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8, ease: "easeOut" }}
className="py-24 bg-white dark:bg-[#0f0f0f] border-t border-gray-100 dark:border-white/5"
>
<div className="max-w-7xl mx-auto px-6">
<div className="mb-16 text-center">
<span className="text-xs font-semibold uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2 block">Why Choose Us</span>
<h2 className="font-display text-3xl md:text-4xl text-gray-900 dark:text-white">
Built on trust. <span className="text-gray-400 dark:text-gray-600">Driven by excellence.</span>
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{features.map((feature, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.5, delay: i * 0.1 }}
whileHover={{ y: -10, boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)" }}
className="p-8 border border-gray-200 dark:border-white/10 rounded-xl bg-gray-50 dark:bg-white/5 hover:bg-gray-100 dark:hover:bg-white/10 hover:border-blue-400 dark:hover:border-blue-400 transition-colors duration-300 group"
>
<motion.div
whileHover={{ rotate: 360, scale: 1.1 }}
transition={{ duration: 0.5 }}
className={`w-12 h-12 rounded-full flex items-center justify-center mb-6 ${getColorClasses(feature.color)}`}
>
<span className="material-symbols-outlined text-2xl">{feature.icon}</span>
</motion.div>
<h3 className="text-xl font-display font-bold text-gray-900 dark:text-white mb-3 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">{feature.title}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed">
{feature.desc}
</p>
</motion.div>
))}
</div>
</div>
</motion.section>
);
};
export default Features;

View File

@@ -12,14 +12,14 @@ const Footer: React.FC = () => {
<span className="font-display font-bold text-lg tracking-tight text-gray-900 dark:text-white">Bay Area Affiliates</span>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 max-w-xs mb-6">
Track every move, analyze your performance, and get real-time coaching. Your dedicated IT partner in the Bay Area.
Providing reliable IT services and solutions to the Coastal Bend community for over 25 years.
</p>
<div className="flex gap-4">
{['X', 'in', 'fb'].map((social) => (
<motion.a
key={social}
href="#"
whileHover={{ y: -5, borderColor: "#3b82f6", color: "#3b82f6" }}
<motion.a
key={social}
href="#"
whileHover={{ y: -5, borderColor: "#ffffff", color: "#ffffff" }}
className="w-8 h-8 flex items-center justify-center rounded border border-gray-300 dark:border-white/20 text-gray-600 dark:text-gray-400 transition-colors"
>
<span className="text-xs font-bold">{social}</span>
@@ -27,15 +27,15 @@ const Footer: React.FC = () => {
))}
</div>
</div>
<div>
<h4 className="text-sm font-bold text-gray-900 dark:text-white mb-6 uppercase tracking-wider">Navigation</h4>
<ul className="space-y-4 text-sm text-gray-600 dark:text-gray-400">
{['About', 'Features', 'Testimonials', 'Pricing'].map((item) => (
{['Services', 'Features', 'Blog', 'Contact'].map((item) => (
<li key={item}>
<motion.a
href="#"
whileHover={{ x: 5, color: "#3b82f6" }}
<motion.a
href="#"
whileHover={{ x: 5, color: "#ffffff" }}
className="inline-block transition-colors"
>
{item}
@@ -44,25 +44,25 @@ const Footer: React.FC = () => {
))}
</ul>
</div>
<div>
<h4 className="text-sm font-bold text-gray-900 dark:text-white mb-6 uppercase tracking-wider">Contact</h4>
<ul className="space-y-4 text-sm text-gray-600 dark:text-gray-400">
<li>support@bayareaaffiliates.com</li>
<li>(361) 765-8400</li>
<li>123 Market St, San Francisco, CA</li>
<li><motion.a whileHover={{ x: 5, color: "#3b82f6" }} href="#" className="inline-block transition-colors">FAQ</motion.a></li>
<li>1001 Blucher St, Corpus Christi, TX 78401</li>
<li><motion.a whileHover={{ x: 5, color: "#ffffff" }} href="#" className="inline-block transition-colors">FAQ</motion.a></li>
</ul>
</div>
</div>
<div className="border-t border-gray-200 dark:border-white/10 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
<p className="text-xs text-gray-500 dark:text-gray-600">
© 2024 Bay Area Affiliates, Inc. All rights reserved.
© 2026 Bay Area Affiliates, Inc. All rights reserved.
</p>
<div className="flex gap-6">
<motion.a whileHover={{ color: "#3b82f6" }} href="#" className="text-xs text-gray-500 dark:text-gray-600 transition-colors">Privacy Policy</motion.a>
<motion.a whileHover={{ color: "#3b82f6" }} href="#" className="text-xs text-gray-500 dark:text-gray-600 transition-colors">Terms of Service</motion.a>
<motion.a whileHover={{ color: "#ffffff" }} href="#" className="text-xs text-gray-500 dark:text-gray-600 transition-colors">Privacy Policy</motion.a>
<motion.a whileHover={{ color: "#ffffff" }} href="#" className="text-xs text-gray-500 dark:text-gray-600 transition-colors">Terms of Service</motion.a>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import React, { useRef, useLayoutEffect } from 'react';
import { motion } from 'framer-motion';
import { motion, useMotionTemplate, useMotionValue } from 'framer-motion';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -7,12 +7,21 @@ gsap.registerPlugin(ScrollTrigger);
const Hero: React.FC = () => {
const containerRef = useRef<HTMLDivElement>(null);
const imageRef = useRef<HTMLImageElement>(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
gsap.to(imageRef.current, {
gsap.to(parallaxWrapperRef.current, {
yPercent: 30,
ease: "none",
scrollTrigger: {
@@ -24,7 +33,7 @@ const Hero: React.FC = () => {
});
// Text Stagger Animation
gsap.fromTo(".hero-stagger",
gsap.fromTo(".hero-stagger",
{ y: 50, opacity: 0 },
{ y: 0, opacity: 1, duration: 1, stagger: 0.2, ease: "power3.out", delay: 0.2 }
);
@@ -34,14 +43,32 @@ const Hero: React.FC = () => {
}, []);
return (
<section ref={containerRef} className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20">
<div className="absolute inset-0 z-0">
<img
ref={imageRef}
alt="Abstract dark technology background"
className="w-full h-[120%] -top-[10%] absolute object-cover opacity-90 dark:opacity-60 brightness-50 contrast-125"
src="https://lh3.googleusercontent.com/aida-public/AB6AXuDTVpQgmoDeaMvQ7ZXqAWwA3243QWiW39J5KBEt-0pHjhbb4q9ICJb9f42oiDD5becHfElI6zFDQXMpaaMm1Ce5aj4IBVAiIz_XfRzdW-ktaVtom2rkjI9e6AGk71D4YpnXcRtifs5DrbmZfkbp7oBt67fLTwtXLZ_qAic5HprmFz1Xnf2E6emY_trL9Hr4SBtAQL5BE8LHCbF0PrBTK960_Tqz2VhSt_CZfI8UmzBUunqFf54LiDvUsxguYJitq5-r0vWHMVt7xTh1"
/>
<section
ref={containerRef}
onMouseMove={handleMouseMove}
className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20 group"
>
<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 - Slightly Brighter */}
<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 - Only visible via mask */}
<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>
@@ -52,32 +79,32 @@ const Hero: React.FC = () => {
<span className="text-xs uppercase tracking-[0.2em] text-gray-600 dark:text-gray-400 font-medium">Established 1998</span>
<span className="h-px w-8 bg-gray-400 dark:bg-gray-500"></span>
</div>
<h1 className="hero-stagger font-display text-5xl md:text-7xl lg:text-8xl font-medium tracking-tighter leading-[1.1] mb-8 text-gray-900 dark:text-white">
Reliable IT Services<br/>
<span className="text-gray-500 dark:text-gray-500">for Over 25 Years.</span>
Reliable IT Services<br />
<span className="text-gray-500 dark:text-gray-500">for Over 25 Years</span>
</h1>
<p className="hero-stagger text-lg md:text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto mb-10 font-light leading-relaxed">
Bay Area Affiliates is your silent partner in technology. We provide the infrastructure that whispers clarity, ensures uptime, and guides your business growth.
Providing top-notch Computer & Networking solutions to the Coastal Bend community.
</p>
<div className="hero-stagger flex flex-col sm:flex-row items-center justify-center gap-4">
<motion.a
href="#services"
<motion.a
href="#services"
className="px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium"
whileHover={{ scale: 1.05, backgroundColor: "#3b82f6", color: "#ffffff" }}
whileTap={{ scale: 0.95 }}
>
Explore Services
IT Services
</motion.a>
<motion.a
href="#contact"
<motion.a
href="#contact"
className="px-8 py-3 bg-transparent border border-gray-300 dark:border-white/20 text-gray-900 dark:text-white rounded-full font-medium"
whileHover={{ scale: 1.05, backgroundColor: "rgba(255,255,255,0.1)", borderColor: "#ffffff" }}
whileTap={{ scale: 0.95 }}
>
Get a Consultation
Get in Touch
</motion.a>
</div>
</div>

View File

@@ -1,49 +1,55 @@
import React from 'react';
import { motion } from 'framer-motion';
import Counter from './Counter';
const Mission: React.FC = () => {
return (
<motion.section
<motion.section
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8, ease: "easeOut" }}
className="py-24 bg-background-light dark:bg-background-dark relative"
className="py-24 bg-background-light dark:bg-background-dark 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="max-w-7xl mx-auto px-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 items-start">
<motion.div
initial={{ opacity: 0, x: -30 }}
initial={{ opacity: 0, x: -200 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.2 }}
>
<div className="flex items-center gap-2 mb-4 text-xs font-semibold uppercase tracking-widest text-gray-500 dark:text-gray-500">
<span className="w-2 h-2 rounded-full bg-blue-500 animate-pulse"></span>
<span className="w-2 h-2 rounded-full bg-gray-400 dark:bg-gray-600"></span>
Our Mission
</div>
<h2 className="font-display text-4xl md:text-5xl font-medium mb-6 leading-tight text-gray-900 dark:text-white">
Harness invisible power <span className="text-gray-400 dark:text-gray-600">to operate faster, focus deeper, and scale effortlessly.</span>
</h2>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.4 }}
className="pt-4"
initial={{ opacity: 0, x: 200 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.4 }}
className="pt-4"
>
<p className="text-lg text-gray-600 dark:text-gray-400 leading-relaxed mb-8">
Technology shouldn't be a hurdle; it should be the wind at your back. From seamless cloud migrations to robust cybersecurity, we handle the complexities so you can focus on what matters most: your business.
</p>
<div className="grid grid-cols-2 gap-8 border-t border-gray-200 dark:border-white/10 pt-8">
<motion.div whileHover={{ scale: 1.05 }} className="cursor-default">
<span className="block text-3xl font-display font-bold mb-2 text-gray-900 dark:text-white">99.9%</span>
<span className="block text-3xl font-display font-bold mb-2 text-gray-900 dark:text-white flex items-center">
<Counter value={99.9} />%
</span>
<span className="text-sm text-gray-500 dark:text-gray-500">Uptime Guarantee</span>
</motion.div>
<motion.div whileHover={{ scale: 1.05 }} className="cursor-default">
<span className="block text-3xl font-display font-bold mb-2 text-gray-900 dark:text-white">24/7</span>
<span className="block text-3xl font-display font-bold mb-2 text-gray-900 dark:text-white flex items-center">
<Counter value={24} />/7
</span>
<span className="text-sm text-gray-500 dark:text-gray-500">Support Availability</span>
</motion.div>
</div>

View File

@@ -1,37 +1,17 @@
import React, { useState } from 'react';
import { motion, useScroll, useMotionValueEvent, useSpring } from 'framer-motion';
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { motion } from 'framer-motion';
const Navbar: React.FC = () => {
const [hidden, setHidden] = useState(false);
const { scrollY, scrollYProgress } = useScroll();
const scaleX = useSpring(scrollYProgress, {
stiffness: 100,
damping: 30,
restDelta: 0.001
});
useMotionValueEvent(scrollY, "change", (latest) => {
const previous = scrollY.getPrevious() || 0;
if (latest > previous && latest > 150) {
setHidden(true);
} else {
setHidden(false);
}
});
const location = useLocation();
const isHome = location.pathname === '/';
return (
<motion.nav
variants={{
visible: { y: 0 },
hidden: { y: "-100%" },
}}
animate={hidden ? "hidden" : "visible"}
transition={{ duration: 0.35, ease: "easeInOut" }}
<nav
className="fixed w-full z-40 top-0 left-0 border-b border-gray-200 dark:border-white/10 bg-white/80 dark:bg-background-dark/80 backdrop-blur-md"
>
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
<div className="flex items-center gap-2">
<Link to="/" className="flex items-center gap-2">
<motion.div
whileHover={{ rotate: 180 }}
transition={{ duration: 0.5 }}
@@ -39,42 +19,30 @@ const Navbar: React.FC = () => {
<span className="material-symbols-outlined text-xl dark:text-white text-black">dns</span>
</motion.div>
<span className="font-display font-bold text-lg tracking-tight">Bay Area Affiliates</span>
</div>
</Link>
<div className="hidden md:flex items-center gap-8 text-sm font-medium text-gray-600 dark:text-gray-400">
{['Services', 'Features', 'Blog', 'Contact'].map((item) => (
<motion.a
{['About', 'Services', 'Blog', 'Contact'].map((item) => (
<Link
key={item}
href={`#${item.toLowerCase()}`}
to={`/${item.toLowerCase()}`}
className="hover:text-black dark:hover:text-white transition-colors relative group px-2 py-1"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
{item}
<motion.span
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="inline-block"
>
{item}
</motion.span>
<span className="absolute -bottom-1 left-0 w-0 h-0.5 bg-black dark:bg-white transition-all duration-300 ease-out group-hover:w-full"></span>
</motion.a>
</Link>
))}
</div>
<motion.a
href="#"
className="text-sm font-medium bg-black dark:bg-white text-white dark:text-black px-4 py-2 rounded-full transition-all"
whileHover={{
scale: 1.05,
boxShadow: "0px 0px 15px rgba(100, 100, 100, 0.5)"
}}
whileTap={{ scale: 0.95 }}
>
Client Portal
</motion.a>
{/* Client Portal button removed */}
</div>
{/* Scroll Progress Indicator */}
<motion.div
className="absolute bottom-0 left-0 right-0 h-[2px] bg-blue-600 dark:bg-blue-500 origin-left z-50"
style={{ scaleX }}
/>
</motion.nav>
</nav>
);
};

View File

@@ -7,144 +7,110 @@ gsap.registerPlugin(ScrollTrigger);
const Process: React.FC = () => {
const containerRef = useRef<HTMLDivElement>(null);
const imageColRef = useRef<HTMLDivElement>(null);
const imgRef = useRef<HTMLImageElement>(null);
const textColRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
const ctx = gsap.context(() => {
// Fade in the whole section
gsap.fromTo(containerRef.current,
{ opacity: 0, y: 50 },
{
opacity: 1,
y: 0,
duration: 1,
ease: "power3.out",
scrollTrigger: {
trigger: containerRef.current,
start: "top 80%",
once: true
const ctx = gsap.context((self) => {
// Dramatic Zoom Animation
if (containerRef.current && imgRef.current) {
gsap.fromTo(imgRef.current,
{ scale: 1, transformOrigin: 'center center' },
{
scale: 2.0,
ease: "none",
scrollTrigger: {
trigger: containerRef.current,
start: "top bottom",
end: "bottom top",
scrub: true,
}
}
}
);
);
}
// Desktop specific animations
const mm = gsap.matchMedia();
mm.add("(min-width: 1024px)", () => {
if (containerRef.current && imageColRef.current && imgRef.current) {
// Pinning logic
ScrollTrigger.create({
trigger: containerRef.current,
start: "top center",
end: "bottom center",
pin: imageColRef.current,
pinSpacing: false,
scrub: true,
});
// Scroll-to-Zoom logic
gsap.fromTo(imgRef.current,
{ scale: 1 },
{
scale: 2.2,
ease: "power1.inOut",
scrollTrigger: {
trigger: containerRef.current,
start: "top bottom",
end: "bottom top",
scrub: 1,
}
// Animate steps - even slower, one by one appearance
const steps = gsap.utils.selector(containerRef.current)('.process-step');
steps.forEach((step: any, index: number) => {
gsap.fromTo(step,
{ opacity: 0, y: 60 },
{
opacity: 1,
y: 0,
duration: 2,
ease: "power3.out",
scrollTrigger: {
trigger: step,
start: "top 95%",
end: "top 40%",
toggleActions: "play reverse play reverse",
scrub: 1.5
}
);
}
// Animate steps as they come into view
const steps = gsap.utils.toArray('.process-step');
steps.forEach((step: any) => {
gsap.fromTo(step,
{ opacity: 0.3, x: 20 },
{
opacity: 1,
x: 0,
duration: 0.5,
scrollTrigger: {
trigger: step,
start: "top 80%",
end: "top 50%",
scrub: 0.5,
toggleActions: "play reverse play reverse"
}
}
);
});
}
);
});
}, containerRef);
return () => ctx.revert();
}, []);
return (
<section ref={containerRef} className="py-24 bg-background-light dark:bg-background-dark overflow-hidden">
<div className="max-w-7xl mx-auto px-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start relative">
{/* Image Column - Will be pinned */}
<div ref={imageColRef} className="relative rounded-2xl overflow-hidden border border-gray-200 dark:border-white/10 shadow-2xl h-[400px] lg:h-[400px] w-full max-w-lg lg:max-w-none mx-auto group z-10">
<img
ref={imgRef}
alt="Close up of server lights in dark room"
className="w-full h-full object-cover opacity-90 will-change-transform origin-center scale-100"
src="https://lh3.googleusercontent.com/aida-public/AB6AXuD6sSjDiHv-HJ5pcRY_PSPYLWxc5ePEHS5R5qi86rya93pqyTkDAG2t__6s26GhoK1LKnSPbBcqi2cfiVn8A02_G47nrQU8REa-C-4oC8wITbxnE79seVsSNsg-6pA58AgqgT4WBextocvu9UYIxQo0Hkymge7c870jS6yGtkUxgJR6RH6_HKnKs03tA7yYClVL2l6ObnPSvfXprP2XRs_GJHWm0bDD_0LhX0eCnXSkUdrYV4_LBvAFosluY1t6lmI2NUcO0diZffZZ"
/>
<div className="absolute bottom-6 left-6 right-6 p-6 bg-white/5 backdrop-blur-md rounded-lg border border-white/10 pointer-events-none">
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-full bg-white/10 flex items-center justify-center flex-shrink-0">
<span className="material-symbols-outlined text-white text-sm">construction</span>
<section ref={containerRef} className="relative w-full" style={{ clipPath: 'inset(0)' }}>
{/* Fixed Background Image - constrained to this section via clip-path */}
<div className="fixed inset-0 w-full h-screen z-0 overflow-hidden">
<img
ref={imgRef}
alt="Modern server rack infrastructure"
className="w-full h-full object-cover opacity-80 will-change-transform origin-center"
src="/src/assets/process-illustration.png"
/>
{/* Gradient overlay for text readability */}
<div className="absolute inset-0 bg-gradient-to-r from-black/50 via-black/30 to-black/60" />
</div>
{/* Content - positioned relative, scrolls over the fixed image */}
<div className="relative z-10 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.03),rgba(255,255,255,0))]">
{/* Header - Static on mobile, fixed on desktop */}
<div className="relative mb-12 lg:mb-0 lg:fixed lg:top-1/2 lg:right-16 lg:-translate-y-1/2 z-20 text-left lg:text-right px-6 lg:px-0" style={{ clipPath: 'none' }}>
<span className="text-xs font-semibold uppercase tracking-widest text-gray-300 mb-2 block">Process</span>
<h2 className="font-display text-3xl lg:text-5xl font-medium text-white">
One consultation to begin,<br />
<span className="text-gray-400">three steps to clarity.</span>
</h2>
</div>
{/* Spacer for first screen - shortened */}
<div className="h-[30vh]" />
{/* Steps - LEFT side on desktop, full width on mobile */}
<div className="min-h-screen px-6 lg:px-16 py-24">
<div className="w-full lg:w-1/2 space-y-[60vh]">
{[
{ num: "1", title: "Audit & Assess", desc: "We dive deep into your current infrastructure to identify vulnerabilities and opportunities for optimization." },
{ num: "2", title: "Implement & Secure", desc: "Our team deploys the necessary hardware and software solutions with minimal disruption to your daily operations." },
{ num: "3", title: "Monitor & Maintain", desc: "Ongoing 24/7 monitoring ensures problems are solved before you even notice them." }
].map((step, i) => (
<div key={i} className="process-step flex gap-6 group cursor-default bg-black/60 backdrop-blur-md p-8 rounded-2xl border border-white/10">
<div className="flex-shrink-0 mt-1">
<motion.span
whileHover={{ scale: 1.2, borderColor: "#3b82f6", color: "#3b82f6" }}
className="flex items-center justify-center w-12 h-12 rounded-xl border-2 border-white/30 text-lg font-bold text-white transition-colors"
>
{step.num}
</motion.span>
</div>
<div>
<h4 className="text-white font-medium mb-1">On-Site Support</h4>
<p className="text-gray-400 text-xs leading-relaxed">Technicians dispatched within 2 hours for critical failures in the Bay Area.</p>
<h3 className="text-2xl lg:text-3xl font-medium text-white group-hover:translate-x-1 transition-transform group-hover:text-blue-400">{step.title}</h3>
<p className="text-lg text-gray-300 mt-3 leading-relaxed max-w-lg">
{step.desc}
</p>
</div>
</div>
</div>
))}
</div>
{/* Text Content */}
<div ref={textColRef} className="lg:py-12">
<span className="text-xs font-semibold uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2 block">Process</span>
<h2 className="font-display text-4xl font-medium mb-6 text-gray-900 dark:text-white">
One consultation to begin,<br/>
<span className="text-gray-400 dark:text-gray-600">three steps to clarity.</span>
</h2>
<div className="space-y-16 mt-16">
{[
{ num: "1", title: "Audit & Assess", desc: "We dive deep into your current infrastructure to identify vulnerabilities and opportunities for optimization." },
{ num: "2", title: "Implement & Secure", desc: "Our team deploys the necessary hardware and software solutions with minimal disruption to your daily operations." },
{ num: "3", title: "Monitor & Maintain", desc: "Ongoing 24/7 monitoring ensures problems are solved before you even notice them." }
].map((step, i) => (
<div key={i} className="process-step flex gap-6 group cursor-default">
<div className="flex-shrink-0 mt-1">
<motion.span
whileHover={{ scale: 1.2, borderColor: "#3b82f6", color: "#3b82f6" }}
className="flex items-center justify-center w-8 h-8 rounded border border-gray-300 dark:border-white/20 text-sm font-medium text-gray-500 dark:text-gray-400 transition-colors"
>
{step.num}
</motion.span>
</div>
<div>
<h3 className="text-xl font-medium text-gray-900 dark:text-white group-hover:translate-x-1 transition-transform group-hover:text-blue-500">{step.title}</h3>
<p className="text-base text-gray-600 dark:text-gray-400 mt-2 leading-relaxed">
{step.desc}
</p>
</div>
</div>
))}
</div>
</div>
</div>
{/* End spacer - shortened */}
<div className="h-[20vh]" />
</div>
</section>
);

View File

@@ -10,31 +10,71 @@ const servicesData = [
id: 1,
category: 'IT Infrastructure',
title: 'Windows 11 Transition',
description: 'Seamless upgrades for your entire fleet. We handle compatibility checks, data backup, and deployment so your workflow never stutters.',
description: 'Upgrade to Windows 11 before October 2025 to ensure continued security support and take advantage of the latest features.',
icon: 'desktop_windows',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBMpd_cFINnFibfNErBs8OVAAyDQYTRXix88YH91QImuGi11XGwlY_QUB2R9htcC1h_fTXUeftdEieGT-oi5p5TBjpAyW-86mSsXu-rqhRTBsJlAGuE37bxJES4DUayktXIToEcF-M4PyXdyyTPIYtpYrxK18b2-sPwMzuzCL0LpgJwd5EoYxAkrJQ7W4eBrIG2e9Cw9sY0dJpXJy-TRgwBG0nk-S7W4Y0s3U9w--AzE4fcUimeGMqWwdCncU5tnETmkrkDNFiCyKSA'
},
{
id: 2,
category: 'Security',
title: 'Web Services & Security',
description: 'From hosting to rigorous penetration testing. Secure your digital storefront with enterprise-grade protection and 99.9% uptime.',
icon: 'security',
category: 'Web Services',
title: 'Web Services',
description: 'Web design, domain registration, email services, and more to establish and enhance your online presence.',
icon: 'language',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCxibXNCB5mU7MdWE5znMWnQUc9-d2ZoYF7LXK1CMssnvaFz2ZsGzyxXMbqDmely-UfxapqILD5-Exeo1wlQZKg8T2MK4vjlyAMaehoJoqTy2hHh8rxj46i8CKb4-ILL2JswBc98nJt_Fo1DfcDH0dHH5Zz6H4R2Jm1deViSW8Sp2zNp1sTc4eRHy1URiSRQFcr1C8rca6dKiuNDuyDiUmmesqHobXGItaBeFjJC-0OatWpKbr0zF-Y5qvk9Yl5FY2KUcDY9AcTfelu'
},
{
id: 3,
category: 'Consulting',
category: 'IT Infrastructure',
title: 'Performance Upgrades',
description: 'Is your hardware holding you back? We analyze bottlenecks and implement strategic upgrades to memory, storage, and networks.',
description: 'Enhance your desktops and laptops with SSDs, maintain your Windows installations, and achieve dramatic performance boosts.',
icon: 'speed',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBs2fGGwp4EkMarA9Uvy7IOqyW0Pzxzt-94Bsr8Tkbem4uHPq-vMEmGgKuEmds2zKwPrw2nVcvL3MjjKYWieLSLh5pVUbbK6T9aDxt2xhvo4trARZobhzoQCJfI-r6aGW_aqfwC5XxOr9VA3YdnNnYEgkfW_TWrUWYa6mD8X0KdVG3sLimA8p7qWxIqUzFFV82twn60rP4OwLdIsc6t1OGnJzjemxL1Aw05aDo6Ckfr0a1oZ2kD4xKeTkG--zUhezvXB9I03l6f3b46'
},
{
id: 4,
category: 'IT Infrastructure',
title: 'Printer & Scanner Installation',
description: 'Professional installation and configuration of printers and scanners to ensure seamless integration into your workflow.',
icon: 'print',
image: '/assets/services/printer-scanner.png'
},
{
id: 5,
category: 'IT Infrastructure',
title: 'New/Refurbished Desktop Hardware',
description: 'Supply and installation of new or refurbished desktop hardware, tailored to meet your business requirements.',
icon: 'computer',
image: '/assets/services/desktop-hardware.png'
},
{
id: 6,
category: 'Security',
title: 'VPN Setup',
description: 'Configure Virtual Private Networks to allow secure remote access to your internal network from anywhere.',
icon: 'vpn_lock',
image: '/assets/services/vpn-setup.png'
},
{
id: 7,
category: 'Networking',
title: 'Network Infrastructure Support',
description: 'Robust network solutions to ensure connectivity, security, and efficiency, including routers, access points, and switches.',
icon: 'lan',
image: '/assets/services/network-infrastructure.png'
},
{
id: 8,
category: 'Networking',
title: 'Network Attached Storage',
description: 'Selection, setup, and maintenance of Network Attached Storage solutions to provide scalable and reliable data storage.',
icon: 'storage',
image: '/assets/services/nas-storage.png'
}
];
const categories = ['All', 'IT Infrastructure', 'Web Development', 'Consulting', 'Security'];
const categories = ['All', 'IT Infrastructure', 'Web Services', 'Security', 'Networking'];
const Services: React.FC = () => {
const Services: React.FC<{ preview?: boolean }> = ({ preview = false }) => {
const [activeCategory, setActiveCategory] = useState('All');
const containerRef = useRef<HTMLDivElement>(null);
const imagesRef = useRef<(HTMLDivElement | null)[]>([]);
@@ -42,15 +82,17 @@ const Services: React.FC = () => {
// Reset refs on render to handle filtering updates
imagesRef.current = [];
const filteredServices = activeCategory === 'All'
? servicesData
const filteredServices = activeCategory === 'All'
? servicesData
: servicesData.filter(s => s.category === activeCategory || (activeCategory === 'Web Development' && s.category === 'Security'));
const displayedServices = preview ? servicesData.slice(0, 3) : filteredServices;
useLayoutEffect(() => {
const ctx = gsap.context(() => {
imagesRef.current.forEach((imgWrapper) => {
if (!imgWrapper) return;
gsap.to(imgWrapper, {
yPercent: 30,
ease: "none",
@@ -68,20 +110,20 @@ const Services: React.FC = () => {
}, [filteredServices]);
return (
<motion.section
<motion.section
ref={containerRef}
id="services"
id="services"
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8, ease: "easeOut" }}
className="py-24 bg-white dark:bg-[#0f0f0f] border-t border-gray-100 dark:border-white/5"
className="py-24 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-7xl mx-auto px-6">
<div className="mb-16">
<span className="text-xs font-semibold uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2 block">Core Offerings</span>
<span className="text-xs font-semibold uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2 block">Our Services</span>
<h2 className="font-display text-3xl md:text-4xl text-gray-900 dark:text-white">
Different paths to explore <span className="text-gray-400 dark:text-gray-600">all guided by one expert team.</span>
Comprehensive IT solutions <span className="text-gray-400 dark:text-gray-600">tailored to your business needs.</span>
</h2>
</div>
@@ -90,15 +132,14 @@ const Services: React.FC = () => {
<button
key={cat}
onClick={() => setActiveCategory(cat)}
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'
}`}
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
<motion.div
layoutId="activeTab"
className="absolute bottom-0 left-0 right-0 h-0.5 bg-black dark:bg-white"
/>
@@ -107,38 +148,41 @@ const Services: React.FC = () => {
))}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div
className="grid grid-cols-1 md:grid-cols-3 gap-6"
>
<AnimatePresence mode="popLayout">
{filteredServices.map((service) => (
{filteredServices.map((service, index) => (
<motion.div
key={service.id}
layout
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
whileHover={{ y: -10, transition: { duration: 0.3 } }}
className="group relative bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl overflow-hidden hover:border-blue-500/50 dark:hover:border-blue-500/50 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 */}
<div className="h-48 bg-gray-200 dark:bg-black/40 overflow-hidden relative">
<div className="h-64 bg-gray-200 dark:bg-black/40 overflow-hidden relative">
{/* Parallax Wrapper */}
<div
<div
ref={el => { if (el) imagesRef.current.push(el); }}
className="w-full h-[140%] -mt-[20%]"
>
<img
src={service.image}
alt={service.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110 opacity-80 group-hover: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>
<div className="p-6 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"
whileHover={{ rotate: 360, backgroundColor: "#3b82f6", color: "white", borderColor: "#3b82f6" }}
whileHover={{ rotate: 360, backgroundColor: "#171717", color: "#ffffff", borderColor: "#171717" }}
transition={{ duration: 0.5 }}
>
<span className="material-symbols-outlined text-sm text-gray-900 dark:text-white group-hover:text-white">{service.icon}</span>
@@ -147,8 +191,8 @@ const Services: React.FC = () => {
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed mb-4">
{service.description}
</p>
<a href="#" className="inline-flex items-center text-xs font-bold uppercase tracking-wide text-gray-900 dark:text-white group-hover:text-blue-500 transition-colors">
Learn More <motion.span
<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">
Learn More <motion.span
className="material-symbols-outlined text-xs ml-1"
animate={{ x: [0, 5, 0] }}
transition={{ repeat: Infinity, duration: 1.5, ease: "easeInOut", repeatDelay: 1 }}
@@ -159,6 +203,17 @@ const Services: React.FC = () => {
))}
</AnimatePresence>
</div>
{preview && (
<div className="mt-12 text-center">
<a
href="/services"
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>
</a>
</div>
)}
</div>
</motion.section>
);

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { motion } from 'framer-motion';
const Testimonials: React.FC = () => {
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))]">
<div className="max-w-5xl mx-auto">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }}
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"
>
{/* Quote Icon */}
<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>
</div>
<div className="flex text-yellow-400 mb-6 gap-1 relative z-10">
{[1, 2, 3, 4, 5].map((star) => (
<span key={star} className="material-symbols-outlined fill-current">star</span>
))}
</div>
<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."
</blockquote>
<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">
SM
</div>
<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>
</div>
</motion.div>
</div>
</section>
);
};
export default Testimonials;