Pushing project code
46
App.tsx
|
|
@ -1,18 +1,17 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { BrowserRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
|
||||||
import Lenis from '@studio-freight/lenis';
|
import Lenis from '@studio-freight/lenis';
|
||||||
import gsap from 'gsap';
|
import gsap from 'gsap';
|
||||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
import { ScrollToPlugin } from 'gsap/ScrollToPlugin';
|
import { ScrollToPlugin } from 'gsap/ScrollToPlugin';
|
||||||
import Navbar from './components/Navbar';
|
import Navbar from './components/Navbar';
|
||||||
import Hero from './components/Hero';
|
|
||||||
import Mission from './components/Mission';
|
|
||||||
import Services from './components/Services';
|
|
||||||
import Process from './components/Process';
|
|
||||||
import Features from './components/Features';
|
|
||||||
import Blog from './components/Blog';
|
|
||||||
import Contact from './components/Contact';
|
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
import BackToTop from './components/BackToTop';
|
import BackToTop from './components/BackToTop';
|
||||||
|
import HomePage from './src/pages/HomePage';
|
||||||
|
import AboutPage from './src/pages/AboutPage';
|
||||||
|
import ServicesPage from './src/pages/ServicesPage';
|
||||||
|
import BlogPage from './src/pages/BlogPage';
|
||||||
|
import ContactPage from './src/pages/ContactPage';
|
||||||
|
|
||||||
// Register GSAP plugins globally
|
// Register GSAP plugins globally
|
||||||
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);
|
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);
|
||||||
|
|
@ -22,7 +21,9 @@ const GrainOverlay = () => (
|
||||||
<div className="fixed inset-0 w-full h-full pointer-events-none z-50 opacity-50 dark:opacity-100 bg-grain mix-blend-overlay"></div>
|
<div className="fixed inset-0 w-full h-full pointer-events-none z-50 opacity-50 dark:opacity-100 bg-grain mix-blend-overlay"></div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function App() {
|
const AppContent: React.FC = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialize Lenis for smooth scrolling
|
// Initialize Lenis for smooth scrolling
|
||||||
const lenis = new Lenis({
|
const lenis = new Lenis({
|
||||||
|
|
@ -48,27 +49,38 @@ export default function App() {
|
||||||
// Disable lag smoothing to prevent jumps
|
// Disable lag smoothing to prevent jumps
|
||||||
gsap.ticker.lagSmoothing(0);
|
gsap.ticker.lagSmoothing(0);
|
||||||
|
|
||||||
|
// Reset scroll on route change
|
||||||
|
lenis.scrollTo(0, { immediate: true });
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
gsap.ticker.remove(ticker);
|
gsap.ticker.remove(ticker);
|
||||||
lenis.destroy();
|
lenis.destroy();
|
||||||
};
|
};
|
||||||
}, []);
|
}, [location.pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full min-h-screen bg-background-light dark:bg-background-dark">
|
<div className="relative w-full min-h-screen bg-background-light dark:bg-background-dark">
|
||||||
<GrainOverlay />
|
<GrainOverlay />
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main>
|
<main>
|
||||||
<Hero />
|
<Routes>
|
||||||
<Mission />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Services />
|
<Route path="/about" element={<AboutPage />} />
|
||||||
<Process />
|
<Route path="/services" element={<ServicesPage />} />
|
||||||
<Features />
|
<Route path="/blog" element={<BlogPage />} />
|
||||||
<Blog />
|
<Route path="/contact" element={<ContactPage />} />
|
||||||
<Contact />
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
<BackToTop />
|
<BackToTop />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<AppContent />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -8,24 +8,24 @@ gsap.registerPlugin(ScrollTrigger);
|
||||||
const posts = [
|
const posts = [
|
||||||
{
|
{
|
||||||
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuARalmRkuoZMBAbavGQgx4a-JhLgXBJ6JSD0U4vycdwaGGV3d-ffUFrdbx2lIbKrYCmS100i7VJ0w5cDHITXYV6w1-pSUPHKL7Jik__TWOIYOnq_4ND5ri7l8SQoaJdjJK9jhYvtxdxrZm6j8t8BNAjvPTaUdUDo4C7QVqcx1KbGvup6cpF8vY1LJ82S_5OMAZ6JgH0rK5bvWpqD3WqPhtqJCUB6d_1gUvluKjotwnNQ03t1dSYV8HOtRrLE83j6i_wgL4GZ0XTsMZb',
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuARalmRkuoZMBAbavGQgx4a-JhLgXBJ6JSD0U4vycdwaGGV3d-ffUFrdbx2lIbKrYCmS100i7VJ0w5cDHITXYV6w1-pSUPHKL7Jik__TWOIYOnq_4ND5ri7l8SQoaJdjJK9jhYvtxdxrZm6j8t8BNAjvPTaUdUDo4C7QVqcx1KbGvup6cpF8vY1LJ82S_5OMAZ6JgH0rK5bvWpqD3WqPhtqJCUB6d_1gUvluKjotwnNQ03t1dSYV8HOtRrLE83j6i_wgL4GZ0XTsMZb',
|
||||||
date: 'Oct 12, 2024',
|
date: 'Jan 10, 2026',
|
||||||
category: 'Cybersecurity',
|
category: 'Performance',
|
||||||
title: 'The Hidden Risks of Remote Work',
|
title: 'Upgrade Your HDD to SSD for Enhanced Performance',
|
||||||
excerpt: 'As remote work becomes permanent, new vulnerabilities emerge. Learn how to secure your distributed workforce effectively.'
|
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',
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCz5lTYjY4RNXubQlrA-BtLIGR3nUY8ULkD9omwT5FShfdMrbMgS5dDCyfN3xiB5WC7T3vjNvyvVbvnD0G1zBpbNTjfOYyhmAEfno7Hf5W1sm-KYRXYrLGQq-c6TkLgEf0i9JGNvuFZ6edcenr2o39dCzIPXcp_z9XWOIzp7kBX2EydNPLJoRofVYuSTmEA1y0_xh4sdiRy1PykRASGLhKfN19_XLNuwyTBVKYISY7cHc-An69eZpAfhrvngu3E47rU6KuQS0k3QXBZ',
|
||||||
date: 'Sep 28, 2024',
|
date: 'Jan 5, 2026',
|
||||||
category: 'Cloud Infrastructure',
|
category: 'Security',
|
||||||
title: 'Migrating to the Cloud: A Step-by-Step Guide',
|
title: 'Secure Your Corporate Network Access with WireGuard VPN',
|
||||||
excerpt: 'Thinking about moving your data? Here is a comprehensive checklist to ensure a smooth and secure transition.'
|
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-',
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCl5iOhTsCqcHnho89DkoLh0DYeuvef0pdp8k26NKzcAq7YPvWbAYARg9mCIvqGTxQGradp8zvscuuibskpz4W_nEzQQO1z7lgwKJ1Xxiw_yQOyXMLfoRNLTHXzqFUH8Q5daCAfYTb7Zl3sFjB7k8i44D6TGolzqrN05Db27Abf2TWDDzHpVSrNml4zddvxholHFxMzqDeSzQ5p77SLDSFNaYBZGR2lEdN2V9O0GzMqxbOjFmBGMW48nlrEDLDzYGv_gWI3RSqNqBl-',
|
||||||
date: 'Sep 15, 2024',
|
date: 'Dec 28, 2025',
|
||||||
category: 'Innovation',
|
category: 'Infrastructure',
|
||||||
title: 'AI in Business: Beyond the Hype',
|
title: 'Virtualizing Windows Machines: Future-Proof Your Corporate Network',
|
||||||
excerpt: 'Artificial Intelligence is transforming industries. Discover practical applications that can drive efficiency in your business today.'
|
excerpt: 'In October 2025, Microsoft will end support for Windows 10. Learn how virtualization can help you prepare...'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -63,14 +63,14 @@ const Blog: React.FC = () => {
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true, margin: "-100px" }}
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
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="max-w-7xl mx-auto px-6">
|
||||||
<div className="flex justify-between items-end mb-12">
|
<div className="flex justify-between items-end mb-12">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-xs font-semibold uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2 block">Latest Insights</span>
|
<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">
|
<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>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<motion.a
|
<motion.a
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -22,7 +22,7 @@ const Contact: React.FC = () => {
|
||||||
Get in Touch
|
Get in Touch
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 dark:text-gray-400 text-lg">
|
<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>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
|
@ -35,35 +35,62 @@ const Contact: React.FC = () => {
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name</label>
|
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name *</label>
|
||||||
<motion.input
|
<motion.input
|
||||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
placeholder="John Doe"
|
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"
|
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>
|
<div>
|
||||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Email</label>
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Email *</label>
|
||||||
<motion.input
|
<motion.input
|
||||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
type="email"
|
type="email"
|
||||||
id="email"
|
id="email"
|
||||||
placeholder="john@company.com"
|
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"
|
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>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="message" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Message</label>
|
<label htmlFor="message" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Message *</label>
|
||||||
<motion.textarea
|
<motion.textarea
|
||||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
id="message"
|
id="message"
|
||||||
placeholder="Tell us about your project..."
|
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"
|
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>
|
></motion.textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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>
|
<span className="font-display font-bold text-lg tracking-tight text-gray-900 dark:text-white">Bay Area Affiliates</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400 max-w-xs mb-6">
|
<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>
|
</p>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
{['X', 'in', 'fb'].map((social) => (
|
{['X', 'in', 'fb'].map((social) => (
|
||||||
<motion.a
|
<motion.a
|
||||||
key={social}
|
key={social}
|
||||||
href="#"
|
href="#"
|
||||||
whileHover={{ y: -5, borderColor: "#3b82f6", color: "#3b82f6" }}
|
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"
|
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>
|
<span className="text-xs font-bold">{social}</span>
|
||||||
|
|
@ -31,11 +31,11 @@ const Footer: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-bold text-gray-900 dark:text-white mb-6 uppercase tracking-wider">Navigation</h4>
|
<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">
|
<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}>
|
<li key={item}>
|
||||||
<motion.a
|
<motion.a
|
||||||
href="#"
|
href="#"
|
||||||
whileHover={{ x: 5, color: "#3b82f6" }}
|
whileHover={{ x: 5, color: "#ffffff" }}
|
||||||
className="inline-block transition-colors"
|
className="inline-block transition-colors"
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
|
|
@ -50,19 +50,19 @@ const Footer: React.FC = () => {
|
||||||
<ul className="space-y-4 text-sm text-gray-600 dark:text-gray-400">
|
<ul className="space-y-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
<li>support@bayareaaffiliates.com</li>
|
<li>support@bayareaaffiliates.com</li>
|
||||||
<li>(361) 765-8400</li>
|
<li>(361) 765-8400</li>
|
||||||
<li>123 Market St, San Francisco, CA</li>
|
<li>1001 Blucher St, Corpus Christi, TX 78401</li>
|
||||||
<li><motion.a whileHover={{ x: 5, color: "#3b82f6" }} href="#" className="inline-block transition-colors">FAQ</motion.a></li>
|
<li><motion.a whileHover={{ x: 5, color: "#ffffff" }} href="#" className="inline-block transition-colors">FAQ</motion.a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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">
|
<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>
|
</p>
|
||||||
<div className="flex gap-6">
|
<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: "#ffffff" }} 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">Terms of Service</motion.a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useRef, useLayoutEffect } from 'react';
|
import React, { useRef, useLayoutEffect } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion, useMotionTemplate, useMotionValue } from 'framer-motion';
|
||||||
import gsap from 'gsap';
|
import gsap from 'gsap';
|
||||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
|
|
@ -7,12 +7,21 @@ gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
const Hero: React.FC = () => {
|
const Hero: React.FC = () => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
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(() => {
|
useLayoutEffect(() => {
|
||||||
const ctx = gsap.context(() => {
|
const ctx = gsap.context(() => {
|
||||||
// Parallax Background
|
// Parallax Background
|
||||||
gsap.to(imageRef.current, {
|
gsap.to(parallaxWrapperRef.current, {
|
||||||
yPercent: 30,
|
yPercent: 30,
|
||||||
ease: "none",
|
ease: "none",
|
||||||
scrollTrigger: {
|
scrollTrigger: {
|
||||||
|
|
@ -34,14 +43,32 @@ const Hero: React.FC = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section ref={containerRef} className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20">
|
<section
|
||||||
<div className="absolute inset-0 z-0">
|
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
|
<img
|
||||||
ref={imageRef}
|
|
||||||
alt="Abstract dark technology background"
|
alt="Abstract dark technology background"
|
||||||
className="w-full h-[120%] -top-[10%] absolute object-cover opacity-90 dark:opacity-60 brightness-50 contrast-125"
|
className="w-full h-full object-cover opacity-90 dark:opacity-70 brightness-75 contrast-150"
|
||||||
src="https://lh3.googleusercontent.com/aida-public/AB6AXuDTVpQgmoDeaMvQ7ZXqAWwA3243QWiW39J5KBEt-0pHjhbb4q9ICJb9f42oiDD5becHfElI6zFDQXMpaaMm1Ce5aj4IBVAiIz_XfRzdW-ktaVtom2rkjI9e6AGk71D4YpnXcRtifs5DrbmZfkbp7oBt67fLTwtXLZ_qAic5HprmFz1Xnf2E6emY_trL9Hr4SBtAQL5BE8LHCbF0PrBTK960_Tqz2VhSt_CZfI8UmzBUunqFf54LiDvUsxguYJitq5-r0vWHMVt7xTh1"
|
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-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 className="absolute inset-0 bg-gradient-to-b from-background-light/50 dark:from-background-dark/50 to-transparent"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -55,11 +82,11 @@ const Hero: React.FC = () => {
|
||||||
|
|
||||||
<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">
|
<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 />
|
Reliable IT Services<br />
|
||||||
<span className="text-gray-500 dark:text-gray-500">for Over 25 Years.</span>
|
<span className="text-gray-500 dark:text-gray-500">for Over 25 Years</span>
|
||||||
</h1>
|
</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">
|
<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>
|
</p>
|
||||||
|
|
||||||
<div className="hero-stagger flex flex-col sm:flex-row items-center justify-center gap-4">
|
<div className="hero-stagger flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||||
|
|
@ -69,7 +96,7 @@ const Hero: React.FC = () => {
|
||||||
whileHover={{ scale: 1.05, backgroundColor: "#3b82f6", color: "#ffffff" }}
|
whileHover={{ scale: 1.05, backgroundColor: "#3b82f6", color: "#ffffff" }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
>
|
>
|
||||||
Explore Services
|
IT Services
|
||||||
</motion.a>
|
</motion.a>
|
||||||
<motion.a
|
<motion.a
|
||||||
href="#contact"
|
href="#contact"
|
||||||
|
|
@ -77,7 +104,7 @@ const Hero: React.FC = () => {
|
||||||
whileHover={{ scale: 1.05, backgroundColor: "rgba(255,255,255,0.1)", borderColor: "#ffffff" }}
|
whileHover={{ scale: 1.05, backgroundColor: "rgba(255,255,255,0.1)", borderColor: "#ffffff" }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
>
|
>
|
||||||
Get a Consultation
|
Get in Touch
|
||||||
</motion.a>
|
</motion.a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
import Counter from './Counter';
|
||||||
|
|
||||||
const Mission: React.FC = () => {
|
const Mission: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<motion.section
|
<motion.section
|
||||||
|
|
@ -8,18 +10,18 @@ const Mission: React.FC = () => {
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true, margin: "-100px" }}
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
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="max-w-7xl mx-auto px-6">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 items-start">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 items-start">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: -30 }}
|
initial={{ opacity: 0, x: -200 }}
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.8, delay: 0.2 }}
|
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">
|
<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
|
Our Mission
|
||||||
</div>
|
</div>
|
||||||
<h2 className="font-display text-4xl md:text-5xl font-medium mb-6 leading-tight text-gray-900 dark:text-white">
|
<h2 className="font-display text-4xl md:text-5xl font-medium mb-6 leading-tight text-gray-900 dark:text-white">
|
||||||
|
|
@ -28,7 +30,7 @@ const Mission: React.FC = () => {
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: 30 }}
|
initial={{ opacity: 0, x: 200 }}
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.8, delay: 0.4 }}
|
transition={{ duration: 0.8, delay: 0.4 }}
|
||||||
|
|
@ -39,11 +41,15 @@ const Mission: React.FC = () => {
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-8 border-t border-gray-200 dark:border-white/10 pt-8">
|
<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">
|
<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>
|
<span className="text-sm text-gray-500 dark:text-gray-500">Uptime Guarantee</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div whileHover={{ scale: 1.05 }} className="cursor-default">
|
<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>
|
<span className="text-sm text-gray-500 dark:text-gray-500">Support Availability</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,17 @@
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { motion, useScroll, useMotionValueEvent, useSpring } from 'framer-motion';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
const Navbar: React.FC = () => {
|
const Navbar: React.FC = () => {
|
||||||
const [hidden, setHidden] = useState(false);
|
const location = useLocation();
|
||||||
const { scrollY, scrollYProgress } = useScroll();
|
const isHome = location.pathname === '/';
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.nav
|
<nav
|
||||||
variants={{
|
|
||||||
visible: { y: 0 },
|
|
||||||
hidden: { y: "-100%" },
|
|
||||||
}}
|
|
||||||
animate={hidden ? "hidden" : "visible"}
|
|
||||||
transition={{ duration: 0.35, ease: "easeInOut" }}
|
|
||||||
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"
|
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="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
|
<motion.div
|
||||||
whileHover={{ rotate: 180 }}
|
whileHover={{ rotate: 180 }}
|
||||||
transition={{ duration: 0.5 }}
|
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>
|
<span className="material-symbols-outlined text-xl dark:text-white text-black">dns</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<span className="font-display font-bold text-lg tracking-tight">Bay Area Affiliates</span>
|
<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">
|
<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) => (
|
{['About', 'Services', 'Blog', 'Contact'].map((item) => (
|
||||||
<motion.a
|
<Link
|
||||||
key={item}
|
key={item}
|
||||||
href={`#${item.toLowerCase()}`}
|
to={`/${item.toLowerCase()}`}
|
||||||
className="hover:text-black dark:hover:text-white transition-colors relative group px-2 py-1"
|
className="hover:text-black dark:hover:text-white transition-colors relative group px-2 py-1"
|
||||||
|
>
|
||||||
|
<motion.span
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
|
className="inline-block"
|
||||||
>
|
>
|
||||||
{item}
|
{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>
|
<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>
|
</div>
|
||||||
|
|
||||||
<motion.a
|
{/* Client Portal button removed */}
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
{/* 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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,135 +7,100 @@ gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
const Process: React.FC = () => {
|
const Process: React.FC = () => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const imageColRef = useRef<HTMLDivElement>(null);
|
|
||||||
const imgRef = useRef<HTMLImageElement>(null);
|
const imgRef = useRef<HTMLImageElement>(null);
|
||||||
const textColRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const ctx = gsap.context(() => {
|
const ctx = gsap.context((self) => {
|
||||||
// Fade in the whole section
|
// Dramatic Zoom Animation
|
||||||
gsap.fromTo(containerRef.current,
|
if (containerRef.current && imgRef.current) {
|
||||||
{ opacity: 0, y: 50 },
|
|
||||||
{
|
|
||||||
opacity: 1,
|
|
||||||
y: 0,
|
|
||||||
duration: 1,
|
|
||||||
ease: "power3.out",
|
|
||||||
scrollTrigger: {
|
|
||||||
trigger: containerRef.current,
|
|
||||||
start: "top 80%",
|
|
||||||
once: 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,
|
gsap.fromTo(imgRef.current,
|
||||||
{ scale: 1 },
|
{ scale: 1, transformOrigin: 'center center' },
|
||||||
{
|
{
|
||||||
scale: 2.2,
|
scale: 2.0,
|
||||||
ease: "power1.inOut",
|
ease: "none",
|
||||||
scrollTrigger: {
|
scrollTrigger: {
|
||||||
trigger: containerRef.current,
|
trigger: containerRef.current,
|
||||||
start: "top bottom",
|
start: "top bottom",
|
||||||
end: "bottom top",
|
end: "bottom top",
|
||||||
scrub: 1,
|
scrub: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate steps as they come into view
|
// Animate steps - even slower, one by one appearance
|
||||||
const steps = gsap.utils.toArray('.process-step');
|
const steps = gsap.utils.selector(containerRef.current)('.process-step');
|
||||||
steps.forEach((step: any) => {
|
steps.forEach((step: any, index: number) => {
|
||||||
gsap.fromTo(step,
|
gsap.fromTo(step,
|
||||||
{ opacity: 0.3, x: 20 },
|
{ opacity: 0, y: 60 },
|
||||||
{
|
{
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
x: 0,
|
y: 0,
|
||||||
duration: 0.5,
|
duration: 2,
|
||||||
|
ease: "power3.out",
|
||||||
scrollTrigger: {
|
scrollTrigger: {
|
||||||
trigger: step,
|
trigger: step,
|
||||||
start: "top 80%",
|
start: "top 95%",
|
||||||
end: "top 50%",
|
end: "top 40%",
|
||||||
scrub: 0.5,
|
toggleActions: "play reverse play reverse",
|
||||||
toggleActions: "play reverse play reverse"
|
scrub: 1.5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}, containerRef);
|
}, containerRef);
|
||||||
return () => ctx.revert();
|
return () => ctx.revert();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section ref={containerRef} className="py-24 bg-background-light dark:bg-background-dark overflow-hidden">
|
<section ref={containerRef} className="relative w-full" style={{ clipPath: 'inset(0)' }}>
|
||||||
<div className="max-w-7xl mx-auto px-6">
|
{/* Fixed Background Image - constrained to this section via clip-path */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start relative">
|
<div className="fixed inset-0 w-full h-screen z-0 overflow-hidden">
|
||||||
|
|
||||||
{/* 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
|
<img
|
||||||
ref={imgRef}
|
ref={imgRef}
|
||||||
alt="Close up of server lights in dark room"
|
alt="Modern server rack infrastructure"
|
||||||
className="w-full h-full object-cover opacity-90 will-change-transform origin-center scale-100"
|
className="w-full h-full object-cover opacity-80 will-change-transform origin-center"
|
||||||
src="https://lh3.googleusercontent.com/aida-public/AB6AXuD6sSjDiHv-HJ5pcRY_PSPYLWxc5ePEHS5R5qi86rya93pqyTkDAG2t__6s26GhoK1LKnSPbBcqi2cfiVn8A02_G47nrQU8REa-C-4oC8wITbxnE79seVsSNsg-6pA58AgqgT4WBextocvu9UYIxQo0Hkymge7c870jS6yGtkUxgJR6RH6_HKnKs03tA7yYClVL2l6ObnPSvfXprP2XRs_GJHWm0bDD_0LhX0eCnXSkUdrYV4_LBvAFosluY1t6lmI2NUcO0diZffZZ"
|
src="/src/assets/process-illustration.png"
|
||||||
/>
|
/>
|
||||||
<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">
|
{/* Gradient overlay for text readability */}
|
||||||
<div className="flex items-start gap-4">
|
<div className="absolute inset-0 bg-gradient-to-r from-black/50 via-black/30 to-black/60" />
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Text Content */}
|
{/* Content - positioned relative, scrolls over the fixed image */}
|
||||||
<div ref={textColRef} className="lg:py-12">
|
<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))]">
|
||||||
<span className="text-xs font-semibold uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2 block">Process</span>
|
{/* Header - Static on mobile, fixed on desktop */}
|
||||||
<h2 className="font-display text-4xl font-medium mb-6 text-gray-900 dark:text-white">
|
<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 />
|
One consultation to begin,<br />
|
||||||
<span className="text-gray-400 dark:text-gray-600">three steps to clarity.</span>
|
<span className="text-gray-400">three steps to clarity.</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-16 mt-16">
|
{/* 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: "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: "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." }
|
{ num: "3", title: "Monitor & Maintain", desc: "Ongoing 24/7 monitoring ensures problems are solved before you even notice them." }
|
||||||
].map((step, i) => (
|
].map((step, i) => (
|
||||||
<div key={i} className="process-step flex gap-6 group cursor-default">
|
<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">
|
<div className="flex-shrink-0 mt-1">
|
||||||
<motion.span
|
<motion.span
|
||||||
whileHover={{ scale: 1.2, borderColor: "#3b82f6", color: "#3b82f6" }}
|
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"
|
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}
|
{step.num}
|
||||||
</motion.span>
|
</motion.span>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<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-base text-gray-600 dark:text-gray-400 mt-2 leading-relaxed">
|
<p className="text-lg text-gray-300 mt-3 leading-relaxed max-w-lg">
|
||||||
{step.desc}
|
{step.desc}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -144,7 +109,8 @@ const Process: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
{/* End spacer - shortened */}
|
||||||
|
<div className="h-[20vh]" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,31 +10,71 @@ const servicesData = [
|
||||||
id: 1,
|
id: 1,
|
||||||
category: 'IT Infrastructure',
|
category: 'IT Infrastructure',
|
||||||
title: 'Windows 11 Transition',
|
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',
|
icon: 'desktop_windows',
|
||||||
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBMpd_cFINnFibfNErBs8OVAAyDQYTRXix88YH91QImuGi11XGwlY_QUB2R9htcC1h_fTXUeftdEieGT-oi5p5TBjpAyW-86mSsXu-rqhRTBsJlAGuE37bxJES4DUayktXIToEcF-M4PyXdyyTPIYtpYrxK18b2-sPwMzuzCL0LpgJwd5EoYxAkrJQ7W4eBrIG2e9Cw9sY0dJpXJy-TRgwBG0nk-S7W4Y0s3U9w--AzE4fcUimeGMqWwdCncU5tnETmkrkDNFiCyKSA'
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBMpd_cFINnFibfNErBs8OVAAyDQYTRXix88YH91QImuGi11XGwlY_QUB2R9htcC1h_fTXUeftdEieGT-oi5p5TBjpAyW-86mSsXu-rqhRTBsJlAGuE37bxJES4DUayktXIToEcF-M4PyXdyyTPIYtpYrxK18b2-sPwMzuzCL0LpgJwd5EoYxAkrJQ7W4eBrIG2e9Cw9sY0dJpXJy-TRgwBG0nk-S7W4Y0s3U9w--AzE4fcUimeGMqWwdCncU5tnETmkrkDNFiCyKSA'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
category: 'Security',
|
category: 'Web Services',
|
||||||
title: 'Web Services & Security',
|
title: 'Web Services',
|
||||||
description: 'From hosting to rigorous penetration testing. Secure your digital storefront with enterprise-grade protection and 99.9% uptime.',
|
description: 'Web design, domain registration, email services, and more to establish and enhance your online presence.',
|
||||||
icon: 'security',
|
icon: 'language',
|
||||||
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCxibXNCB5mU7MdWE5znMWnQUc9-d2ZoYF7LXK1CMssnvaFz2ZsGzyxXMbqDmely-UfxapqILD5-Exeo1wlQZKg8T2MK4vjlyAMaehoJoqTy2hHh8rxj46i8CKb4-ILL2JswBc98nJt_Fo1DfcDH0dHH5Zz6H4R2Jm1deViSW8Sp2zNp1sTc4eRHy1URiSRQFcr1C8rca6dKiuNDuyDiUmmesqHobXGItaBeFjJC-0OatWpKbr0zF-Y5qvk9Yl5FY2KUcDY9AcTfelu'
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCxibXNCB5mU7MdWE5znMWnQUc9-d2ZoYF7LXK1CMssnvaFz2ZsGzyxXMbqDmely-UfxapqILD5-Exeo1wlQZKg8T2MK4vjlyAMaehoJoqTy2hHh8rxj46i8CKb4-ILL2JswBc98nJt_Fo1DfcDH0dHH5Zz6H4R2Jm1deViSW8Sp2zNp1sTc4eRHy1URiSRQFcr1C8rca6dKiuNDuyDiUmmesqHobXGItaBeFjJC-0OatWpKbr0zF-Y5qvk9Yl5FY2KUcDY9AcTfelu'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
category: 'Consulting',
|
category: 'IT Infrastructure',
|
||||||
title: 'Performance Upgrades',
|
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',
|
icon: 'speed',
|
||||||
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBs2fGGwp4EkMarA9Uvy7IOqyW0Pzxzt-94Bsr8Tkbem4uHPq-vMEmGgKuEmds2zKwPrw2nVcvL3MjjKYWieLSLh5pVUbbK6T9aDxt2xhvo4trARZobhzoQCJfI-r6aGW_aqfwC5XxOr9VA3YdnNnYEgkfW_TWrUWYa6mD8X0KdVG3sLimA8p7qWxIqUzFFV82twn60rP4OwLdIsc6t1OGnJzjemxL1Aw05aDo6Ckfr0a1oZ2kD4xKeTkG--zUhezvXB9I03l6f3b46'
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBs2fGGwp4EkMarA9Uvy7IOqyW0Pzxzt-94Bsr8Tkbem4uHPq-vMEmGgKuEmds2zKwPrw2nVcvL3MjjKYWieLSLh5pVUbbK6T9aDxt2xhvo4trARZobhzoQCJfI-r6aGW_aqfwC5XxOr9VA3YdnNnYEgkfW_TWrUWYa6mD8X0KdVG3sLimA8p7qWxIqUzFFV82twn60rP4OwLdIsc6t1OGnJzjemxL1Aw05aDo6Ckfr0a1oZ2kD4xKeTkG--zUhezvXB9I03l6f3b46'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
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 [activeCategory, setActiveCategory] = useState('All');
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const imagesRef = useRef<(HTMLDivElement | null)[]>([]);
|
const imagesRef = useRef<(HTMLDivElement | null)[]>([]);
|
||||||
|
|
@ -46,6 +86,8 @@ const Services: React.FC = () => {
|
||||||
? servicesData
|
? servicesData
|
||||||
: servicesData.filter(s => s.category === activeCategory || (activeCategory === 'Web Development' && s.category === 'Security'));
|
: servicesData.filter(s => s.category === activeCategory || (activeCategory === 'Web Development' && s.category === 'Security'));
|
||||||
|
|
||||||
|
const displayedServices = preview ? servicesData.slice(0, 3) : filteredServices;
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const ctx = gsap.context(() => {
|
const ctx = gsap.context(() => {
|
||||||
imagesRef.current.forEach((imgWrapper) => {
|
imagesRef.current.forEach((imgWrapper) => {
|
||||||
|
|
@ -75,13 +117,13 @@ const Services: React.FC = () => {
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true, margin: "-100px" }}
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
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="max-w-7xl mx-auto px-6">
|
||||||
<div className="mb-16">
|
<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">
|
<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>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -90,8 +132,7 @@ const Services: React.FC = () => {
|
||||||
<button
|
<button
|
||||||
key={cat}
|
key={cat}
|
||||||
onClick={() => setActiveCategory(cat)}
|
onClick={() => setActiveCategory(cat)}
|
||||||
className={`pb-2 whitespace-nowrap transition-colors relative ${
|
className={`pb-2 whitespace-nowrap transition-colors relative ${activeCategory === cat
|
||||||
activeCategory === cat
|
|
||||||
? 'text-gray-900 dark:text-white'
|
? 'text-gray-900 dark:text-white'
|
||||||
: 'text-gray-500 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-300'
|
: 'text-gray-500 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-300'
|
||||||
}`}
|
}`}
|
||||||
|
|
@ -107,20 +148,23 @@ const Services: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</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">
|
<AnimatePresence mode="popLayout">
|
||||||
{filteredServices.map((service) => (
|
{filteredServices.map((service, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={service.id}
|
key={service.id}
|
||||||
layout
|
layout
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0, scale: 0.9 }}
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
whileHover={{ y: -10, transition: { duration: 0.3 } }}
|
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 */}
|
{/* 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 */}
|
{/* Parallax Wrapper */}
|
||||||
<div
|
<div
|
||||||
ref={el => { if (el) imagesRef.current.push(el); }}
|
ref={el => { if (el) imagesRef.current.push(el); }}
|
||||||
|
|
@ -129,7 +173,7 @@ const Services: React.FC = () => {
|
||||||
<img
|
<img
|
||||||
src={service.image}
|
src={service.image}
|
||||||
alt={service.title}
|
alt={service.title}
|
||||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110 opacity-80 group-hover:opacity-100"
|
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110 opacity-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-gray-50 dark:from-[#161616] to-transparent pointer-events-none"></div>
|
<div className="absolute inset-0 bg-gradient-to-t from-gray-50 dark:from-[#161616] to-transparent pointer-events-none"></div>
|
||||||
|
|
@ -138,7 +182,7 @@ const Services: React.FC = () => {
|
||||||
<div className="p-6 relative">
|
<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"
|
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 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-sm text-gray-900 dark:text-white group-hover:text-white">{service.icon}</span>
|
<span className="material-symbols-outlined text-sm text-gray-900 dark:text-white group-hover:text-white">{service.icon}</span>
|
||||||
|
|
@ -147,7 +191,7 @@ const Services: React.FC = () => {
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed mb-4">
|
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed mb-4">
|
||||||
{service.description}
|
{service.description}
|
||||||
</p>
|
</p>
|
||||||
<a href="#" className="inline-flex items-center text-xs font-bold uppercase tracking-wide text-gray-900 dark:text-white group-hover:text-blue-500 transition-colors">
|
<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
|
Learn More <motion.span
|
||||||
className="material-symbols-outlined text-xs ml-1"
|
className="material-symbols-outlined text-xs ml-1"
|
||||||
animate={{ x: [0, 5, 0] }}
|
animate={{ x: [0, 5, 0] }}
|
||||||
|
|
@ -159,6 +203,17 @@ const Services: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</motion.section>
|
</motion.section>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
15
index.html
|
|
@ -66,21 +66,8 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="importmap">
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"react": "https://esm.sh/react@^19.2.3",
|
|
||||||
"react-dom/": "https://esm.sh/react-dom@^19.2.3/",
|
|
||||||
"react/": "https://esm.sh/react@^19.2.3/",
|
|
||||||
"framer-motion": "https://esm.sh/framer-motion@^12.26.2",
|
|
||||||
"gsap": "https://esm.sh/gsap@^3.14.2",
|
|
||||||
"gsap/": "https://esm.sh/gsap@^3.14.2/",
|
|
||||||
"@studio-freight/lenis": "https://esm.sh/@studio-freight/lenis@^1.0.42"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body class="bg-background-light dark:bg-background-dark text-gray-900 dark:text-white font-sans antialiased selection:bg-white selection:text-black transition-colors duration-300">
|
<body class="bg-background-light dark:bg-background-dark text-gray-900 dark:text-white font-sans antialiased selection:bg-white selection:text-black transition-colors duration-300">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/index.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -9,11 +9,12 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.2.3",
|
"@studio-freight/lenis": "^1.0.42",
|
||||||
"react-dom": "^19.2.3",
|
|
||||||
"framer-motion": "^12.26.2",
|
"framer-motion": "^12.26.2",
|
||||||
"gsap": "^3.14.2",
|
"gsap": "^3.14.2",
|
||||||
"@studio-freight/lenis": "^1.0.42"
|
"react": "^19.2.3",
|
||||||
|
"react-dom": "^19.2.3",
|
||||||
|
"react-router-dom": "^7.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 657 KiB |
|
After Width: | Height: | Size: 516 KiB |
|
After Width: | Height: | Size: 670 KiB |
|
After Width: | Height: | Size: 589 KiB |
|
After Width: | Height: | Size: 720 KiB |
|
After Width: | Height: | Size: 583 KiB |
|
After Width: | Height: | Size: 675 KiB |
|
|
@ -0,0 +1,246 @@
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { motion, useInView, useSpring, useTransform, useScroll, useMotionValueEvent } from 'framer-motion';
|
||||||
|
import Contact from '../../components/Contact';
|
||||||
|
|
||||||
|
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 stats = [
|
||||||
|
{ label: 'Businesses served', value: '150+' },
|
||||||
|
{ label: 'Uptime achieved', value: '99.9%' },
|
||||||
|
{ label: 'Years of service', value: '15+' },
|
||||||
|
{ label: 'Response time', value: '<2min' },
|
||||||
|
];
|
||||||
|
|
||||||
|
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: '2010', title: 'Founded in Corpus Christi', desc: 'Started with a mission to bring enterprise-level IT solutions to local businesses.' },
|
||||||
|
{ year: '2015', title: 'Expanded Service Portfolio', desc: 'Added cloud services and advanced networking to serve growing businesses.' },
|
||||||
|
{ year: '2020', title: 'Remote Work Transformation', desc: 'Helped 100+ businesses transition to secure remote work during the pandemic.' },
|
||||||
|
{ year: '2024', title: 'Leading the Coastal Bend', desc: 'Now serving 150+ businesses with modern, reliable IT infrastructure.' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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 2010, we've been helping businesses in Corpus Christi and surrounding communities build reliable, secure technology foundations that drive growth.
|
||||||
|
</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 the same level of IT expertise and reliability as large corporations, but with the personal touch that only comes from working with your neighbors.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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 serve over 150 businesses across the region, from Corpus Christi to the smallest coastal communities. Our team combines deep technical expertise with real-world business understanding to deliver IT solutions that actually work for our clients.
|
||||||
|
</p>
|
||||||
|
</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: 150, suffix: '+' },
|
||||||
|
{ label: 'Uptime achieved', value: 99.9, suffix: '%' },
|
||||||
|
{ label: 'Years of service', value: 15, suffix: '+' },
|
||||||
|
{ label: 'Response time', value: 2, prefix: '<', suffix: 'min' },
|
||||||
|
].map((stat, index) => (
|
||||||
|
<div key={index} className="p-4">
|
||||||
|
<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>
|
||||||
|
))}
|
||||||
|
</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;
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import Contact from '../../components/Contact';
|
||||||
|
|
||||||
|
const posts = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Upgrade your HDD to SSD for a big speed boost',
|
||||||
|
excerpt: 'A practical checklist for Corpus Christi business owners considering SSD upgrades, including before/after performance comparisons and cost analysis.',
|
||||||
|
image: '/assets/services/desktop-hardware.png',
|
||||||
|
category: 'Hardware',
|
||||||
|
readTime: '5 min read',
|
||||||
|
date: 'Jan 15, 2026'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Secure your corporate network access with WireGuard VPN',
|
||||||
|
excerpt: 'Learn why Corpus Christi businesses are switching to WireGuard VPN for faster, more secure remote access, and how to implement it properly in the Coastal Bend.',
|
||||||
|
image: '/assets/services/vpn-setup.png',
|
||||||
|
category: 'Security',
|
||||||
|
readTime: '7 min read',
|
||||||
|
date: 'Jan 10, 2026'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: 'What comprehensive IT support looks like for SMBs',
|
||||||
|
excerpt: 'Understanding the full scope of managed IT services for Corpus Christi small businesses: from hardware and network infrastructure to virtualization and helpdesk support.',
|
||||||
|
image: '/assets/services/network-infrastructure.png',
|
||||||
|
category: 'Strategy',
|
||||||
|
readTime: '6 min read',
|
||||||
|
date: 'Jan 05, 2026'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const BlogPage: React.FC = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-20 min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
|
||||||
|
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" />
|
||||||
|
<div className="absolute top-[300px] left-1/2 -translate-x-1/2 w-[800px] h-[800px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[120px] pointer-events-none" />
|
||||||
|
<section className="py-20 px-6 bg-white dark:bg-[#0f0f0f] border-b border-gray-100 dark:border-white/5 relative bg-transparent">
|
||||||
|
<div className="max-w-4xl mx-auto text-center relative z-10">
|
||||||
|
<span className="text-blue-600 dark:text-blue-400 font-bold tracking-widest uppercase text-sm mb-3 block">Latest Insights</span>
|
||||||
|
<h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white">
|
||||||
|
Tech insights for the <br /><span className="text-gray-400">Coastal Bend business.</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-16 px-6 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
|
||||||
|
<div className="max-w-5xl mx-auto space-y-16">
|
||||||
|
{posts.map((post) => (
|
||||||
|
<motion.div
|
||||||
|
key={post.id}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
|
whileHover={{ y: -5 }}
|
||||||
|
className="group grid md:grid-cols-2 gap-0 bg-white dark:bg-[#161616] rounded-3xl overflow-hidden shadow-lg border border-gray-100 dark:border-white/5 hover:shadow-2xl hover:shadow-blue-900/10 transition-all duration-300"
|
||||||
|
>
|
||||||
|
<div className="h-64 md:h-auto overflow-hidden relative">
|
||||||
|
<img
|
||||||
|
src={post.image}
|
||||||
|
alt={post.title}
|
||||||
|
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
<div className="absolute top-4 left-4">
|
||||||
|
<span className="px-3 py-1 bg-black/70 backdrop-blur-md text-white text-xs font-bold rounded-full border border-white/20">
|
||||||
|
{post.category}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 md:p-12 flex flex-col justify-center">
|
||||||
|
<div className="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400 mb-4">
|
||||||
|
<span>{post.date}</span>
|
||||||
|
<span className="w-1 h-1 rounded-full bg-gray-300 dark:bg-gray-600"></span>
|
||||||
|
<span>{post.readTime}</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
|
||||||
|
{post.title}
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-600 dark:text-gray-300 leading-relaxed mb-8">
|
||||||
|
{post.excerpt}
|
||||||
|
</p>
|
||||||
|
<div className="mt-auto">
|
||||||
|
<span className="inline-flex items-center gap-2 font-bold text-blue-600 dark:text-white group-hover:gap-3 transition-all">
|
||||||
|
Read article <span className="material-symbols-outlined text-sm">arrow_forward</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Contact />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BlogPage;
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
const ContactPage: React.FC = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
{ q: 'How quickly can you start?', a: 'Most assessments can begin within 48 hours of contact. Emergency support is available 24/7.' },
|
||||||
|
{ q: 'How do you price services?', a: 'Transparent monthly pricing based on devices and services needed. No hidden fees or surprise charges.' },
|
||||||
|
{ q: 'What\'s included in support?', a: '24/7 monitoring, helpdesk, proactive maintenance, security updates, and SLA guarantees.' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-20 min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
|
||||||
|
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" />
|
||||||
|
<div className="absolute bottom-0 right-0 w-[500px] h-[500px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[100px] pointer-events-none" />
|
||||||
|
{/* Hero */}
|
||||||
|
<section className="py-20 px-6 text-center border-b border-white/5 relative z-10">
|
||||||
|
<h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-white">
|
||||||
|
Let's talk about <br /><span className="text-gray-500">your IT needs</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-gray-400 max-w-2xl mx-auto">
|
||||||
|
Ready to improve your technology? We're here to help. Get started with a free consultation and see how we can make your IT work better for you.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-24 px-6 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
|
||||||
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16">
|
||||||
|
|
||||||
|
{/* Left: Contact Form */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
className="bg-white/5 backdrop-blur-sm p-8 md:p-10 rounded-3xl shadow-xl border border-white/10"
|
||||||
|
>
|
||||||
|
<h3 className="text-2xl font-bold mb-8 text-white">Send us a message</h3>
|
||||||
|
<form className="space-y-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">Name *</label>
|
||||||
|
<input type="text" id="name" required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="John Doe" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="phone" className="block text-sm font-medium text-gray-300 mb-2">Phone</label>
|
||||||
|
<input type="tel" id="phone" className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="(555) 000-0000" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-gray-300 mb-2">Email *</label>
|
||||||
|
<input type="email" id="email" required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="john@company.com" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="company" className="block text-sm font-medium text-gray-300 mb-2">Company</label>
|
||||||
|
<input type="text" id="company" className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="Acme Inc." />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="message" className="block text-sm font-medium text-gray-300 mb-2">Message *</label>
|
||||||
|
<textarea id="message" rows={4} required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600 resize-none" placeholder="How can we help you?"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="w-full py-4 bg-white text-black font-bold rounded-xl transition-all hover:bg-gray-200 hover:scale-[1.02] shadow-[0_0_20px_rgba(255,255,255,0.1)]">
|
||||||
|
Send Message
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Right: FAQ & Info */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: 20 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ delay: 0.2 }}
|
||||||
|
className="space-y-12"
|
||||||
|
>
|
||||||
|
{/* Contact Info */}
|
||||||
|
<div className="grid sm:grid-cols-2 gap-8">
|
||||||
|
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors">
|
||||||
|
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4">
|
||||||
|
<span className="material-symbols-outlined">call</span>
|
||||||
|
</div>
|
||||||
|
<h4 className="font-bold text-white mb-1">Phone</h4>
|
||||||
|
<p className="text-gray-400">(361) 765-8400</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors">
|
||||||
|
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4">
|
||||||
|
<span className="material-symbols-outlined">location_on</span>
|
||||||
|
</div>
|
||||||
|
<h4 className="font-bold text-white mb-1">Address</h4>
|
||||||
|
<p className="text-gray-400">1001 Blucher St,<br />Corpus Christi, TX 78401</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10">
|
||||||
|
<h4 className="font-bold text-white mb-4">Hours & Area</h4>
|
||||||
|
<p className="text-gray-400 mb-2"><span className="font-semibold text-white">Mon - Fri:</span> 8:00 AM - 6:00 PM</p>
|
||||||
|
<p className="text-gray-500 text-sm mb-4">(Emergency support: 24/7)</p>
|
||||||
|
<p className="text-gray-400"><span className="font-semibold text-white">Service Area:</span> Corpus Christi & the Coastal Bend (including Portland, Rockport, Aransas Pass, Kingsville, Port Aransas)</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* FAQ */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-bold mb-6 text-white">Quick Answers</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{faqs.map((faq, index) => (
|
||||||
|
<div key={index} className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/30 transition-colors">
|
||||||
|
<h4 className="font-bold text-white mb-2">{faq.q}</h4>
|
||||||
|
<p className="text-gray-400 text-sm leading-relaxed">{faq.a}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* What Happens Next Section */}
|
||||||
|
<section className="py-24 px-6 border-t border-white/5">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<h2 className="font-display text-3xl md:text-4xl font-bold text-center mb-16 text-white">
|
||||||
|
What happens next?
|
||||||
|
</h2>
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
step: "01",
|
||||||
|
title: "We respond quickly",
|
||||||
|
description: "Get a response within 24 hours, usually much faster."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: "02",
|
||||||
|
title: "Free consultation",
|
||||||
|
description: "20-minute call to understand your needs and challenges."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: "03",
|
||||||
|
title: "Custom proposal",
|
||||||
|
description: "Tailored solution with clear next steps and pricing."
|
||||||
|
}
|
||||||
|
].map((item, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={index}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: index * 0.1 }}
|
||||||
|
className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10 hover:bg-white/10 transition-colors relative overflow-hidden group"
|
||||||
|
>
|
||||||
|
<div className="absolute top-0 right-0 p-8 opacity-10 font-display text-8xl font-bold text-white group-hover:scale-110 transition-transform select-none pointer-events-none">
|
||||||
|
{item.step}
|
||||||
|
</div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center text-white mb-6 border border-white/10">
|
||||||
|
<span className="material-symbols-outlined">
|
||||||
|
{index === 0 ? 'schedule_send' : index === 1 ? 'phone_in_talk' : 'contract_edit'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-3">{item.title}</h3>
|
||||||
|
<p className="text-gray-400 leading-relaxed">{item.description}</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Service Area Section */}
|
||||||
|
<section className="py-24 px-6 relative overflow-hidden">
|
||||||
|
<div className="absolute inset-0 bg-white/5"></div>
|
||||||
|
<div className="max-w-7xl mx-auto relative z-10">
|
||||||
|
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||||
|
<div>
|
||||||
|
<h2 className="font-display text-3xl md:text-4xl font-bold mb-6 text-white">
|
||||||
|
Our Service Area
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-gray-400 mb-8 leading-relaxed">
|
||||||
|
Proudly serving Corpus Christi, Portland, Rockport, Aransas Pass, Kingsville, Port Aransas, and the entire Coastal Bend region.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{['Corpus Christi', 'Portland', 'Rockport', 'Aransas Pass', 'Kingsville', 'Port Aransas'].map((city) => (
|
||||||
|
<div key={city} className="flex items-center gap-2 px-4 py-2 rounded-full bg-white/5 border border-white/10 text-gray-300">
|
||||||
|
<span className="material-symbols-outlined text-sm text-white">location_on</span>
|
||||||
|
{city}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="h-[400px] bg-[#1a1a1a] rounded-3xl overflow-hidden border border-white/10 shadow-2xl relative">
|
||||||
|
<iframe
|
||||||
|
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3532.1843778537446!2d-97.39864222453538!3d27.79426697613324!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x86685f8090787b65%3A0x6762397985799732!2s1001%20Blucher%20St%2C%20Corpus%20Christi%2C%20TX%2078401!5e0!3m2!1sen!2sus!4v1709420000000!5m2!1sen!2sus"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{ border: 0 }}
|
||||||
|
allowFullScreen
|
||||||
|
loading="lazy"
|
||||||
|
referrerPolicy="no-referrer-when-downgrade"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContactPage;
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import Hero from '../../components/Hero';
|
||||||
|
import Mission from '../../components/Mission';
|
||||||
|
import Services from '../../components/Services';
|
||||||
|
import Process from '../../components/Process';
|
||||||
|
import Blog from '../../components/Blog';
|
||||||
|
import Testimonials from '../../components/Testimonials';
|
||||||
|
import CTA from '../../components/CTA';
|
||||||
|
|
||||||
|
const HomePage: React.FC = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Hero />
|
||||||
|
<Mission />
|
||||||
|
<Services preview={true} />
|
||||||
|
<Process />
|
||||||
|
<Blog />
|
||||||
|
<Testimonials />
|
||||||
|
<CTA />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
|
|
@ -0,0 +1,414 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { motion, AnimatePresence, useScroll, useTransform, useMotionValueEvent } from 'framer-motion';
|
||||||
|
import Contact from '../../components/Contact';
|
||||||
|
|
||||||
|
const services = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Windows 11 Transition',
|
||||||
|
description: 'Upgrade to Windows 11 before October 2025 to ensure continued security support and take advantage of the latest features.',
|
||||||
|
challenge: 'Running outdated operating systems leaves your business vulnerable to security threats and compatibility issues.',
|
||||||
|
approach: 'We manage the entire migration process, from hardware compatibility checks to software deployment and user training.',
|
||||||
|
deliverables: [
|
||||||
|
'Hardware compatibility assessment',
|
||||||
|
'Windows 11 deployment and configuration',
|
||||||
|
'Application compatibility testing',
|
||||||
|
'Security policy implementation',
|
||||||
|
'User training sessions'
|
||||||
|
],
|
||||||
|
needs: [
|
||||||
|
'Current device inventory',
|
||||||
|
'Software list',
|
||||||
|
'User schedule for upgrades'
|
||||||
|
],
|
||||||
|
icon: 'desktop_windows',
|
||||||
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBMpd_cFINnFibfNErBs8OVAAyDQYTRXix88YH91QImuGi11XGwlY_QUB2R9htcC1h_fTXUeftdEieGT-oi5p5TBjpAyW-86mSsXu-rqhRTBsJlAGuE37bxJES4DUayktXIToEcF-M4PyXdyyTPIYtpYrxK18b2-sPwMzuzCL0LpgJwd5EoYxAkrJQ7W4eBrIG2e9Cw9sY0dJpXJy-TRgwBG0nk-S7W4Y0s3U9w--AzE4fcUimeGMqWwdCncU5tnETmkrkDNFiCyKSA'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Web Services',
|
||||||
|
description: 'Web design, domain registration, email services, and more to establish and enhance your online presence.',
|
||||||
|
challenge: 'A poor online presence can cost you customers and credibility in a digital-first world.',
|
||||||
|
approach: 'We build professional, responsive websites and manage your digital identity to attract and retain customers.',
|
||||||
|
deliverables: [
|
||||||
|
'Custom website design & development',
|
||||||
|
'Domain registration & management',
|
||||||
|
'Professional email setup (M365/Google)',
|
||||||
|
'SEO optimization basics',
|
||||||
|
'Hosting & maintenance'
|
||||||
|
],
|
||||||
|
needs: [
|
||||||
|
'Brand guidelines / Logo',
|
||||||
|
'Content & copy',
|
||||||
|
'Domain access (if existing)'
|
||||||
|
],
|
||||||
|
icon: 'language',
|
||||||
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCxibXNCB5mU7MdWE5znMWnQUc9-d2ZoYF7LXK1CMssnvaFz2ZsGzyxXMbqDmely-UfxapqILD5-Exeo1wlQZKg8T2MK4vjlyAMaehoJoqTy2hHh8rxj46i8CKb4-ILL2JswBc98nJt_Fo1DfcDH0dHH5Zz6H4R2Jm1deViSW8Sp2zNp1sTc4eRHy1URiSRQFcr1C8rca6dKiuNDuyDiUmmesqHobXGItaBeFjJC-0OatWpKbr0zF-Y5qvk9Yl5FY2KUcDY9AcTfelu'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: 'Performance Upgrades',
|
||||||
|
description: 'Enhance your desktops and laptops with SSDs, maintain your Windows installations, and achieve dramatic performance boosts.',
|
||||||
|
challenge: 'Slow computers kill productivity and frustrate employees, leading to wasted time.',
|
||||||
|
approach: 'We breathe new life into existing hardware with cost-effective upgrades and optimizations.',
|
||||||
|
deliverables: [
|
||||||
|
'SSD installation & cloning',
|
||||||
|
'RAM upgrades',
|
||||||
|
'System cleanup & optimization',
|
||||||
|
'Thermal paste replacement',
|
||||||
|
'Benchmark reporting'
|
||||||
|
],
|
||||||
|
needs: [
|
||||||
|
'Access to devices',
|
||||||
|
'Data backup confirmation'
|
||||||
|
],
|
||||||
|
icon: 'speed',
|
||||||
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBs2fGGwp4EkMarA9Uvy7IOqyW0Pzxzt-94Bsr8Tkbem4uHPq-vMEmGgKuEmds2zKwPrw2nVcvL3MjjKYWieLSLh5pVUbbK6T9aDxt2xhvo4trARZobhzoQCJfI-r6aGW_aqfwC5XxOr9VA3YdnNnYEgkfW_TWrUWYa6mD8X0KdVG3sLimA8p7qWxIqUzFFV82twn60rP4OwLdIsc6t1OGnJzjemxL1Aw05aDo6Ckfr0a1oZ2kD4xKeTkG--zUhezvXB9I03l6f3b46'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: 'Printer & Scanner Installation',
|
||||||
|
description: 'Professional installation and configuration of printers and scanners to ensure seamless integration into your workflow.',
|
||||||
|
challenge: 'Printer connectivity issues are a leading cause of office support tickets and downtime.',
|
||||||
|
approach: 'We set up reliable printing environments with proper drivers, networking, and user access controls.',
|
||||||
|
deliverables: [
|
||||||
|
'Network printer setup',
|
||||||
|
'Scanner configuration (Scan-to-Email/Folder)',
|
||||||
|
'Print server management',
|
||||||
|
'One-click user deployment',
|
||||||
|
'Troubleshooting training'
|
||||||
|
],
|
||||||
|
needs: [
|
||||||
|
'Printer/Scanner hardware',
|
||||||
|
'Network access details'
|
||||||
|
],
|
||||||
|
icon: 'print',
|
||||||
|
image: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'New/Refurbished Desktop Hardware',
|
||||||
|
description: 'Supply and installation of new or refurbished desktop hardware, tailored to meet your business requirements.',
|
||||||
|
challenge: 'Sourcing the right hardware at the right price can be time-consuming and risky.',
|
||||||
|
approach: 'We source high-quality new and refurbished equipment that meets your specs and budget, fully tested and ready to go.',
|
||||||
|
deliverables: [
|
||||||
|
'Hardware procurement',
|
||||||
|
'Quality assurance testing',
|
||||||
|
'Image deployment',
|
||||||
|
'Peripherals setup',
|
||||||
|
'Warranty management'
|
||||||
|
],
|
||||||
|
needs: [
|
||||||
|
'Budget constraints',
|
||||||
|
'Performance requirements'
|
||||||
|
],
|
||||||
|
icon: 'computer',
|
||||||
|
image: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: 'VPN Setup',
|
||||||
|
description: 'Configure Virtual Private Networks to allow secure remote access to your internal network from anywhere.',
|
||||||
|
challenge: 'Remote work requires secure access to internal resources without exposing your network to threats.',
|
||||||
|
approach: 'We implement robust VPN solutions like WireGuard or OpenVPN for secure, encrypted remote connectivity.',
|
||||||
|
deliverables: [
|
||||||
|
'VPN server configuration',
|
||||||
|
'Client software deployment',
|
||||||
|
'Access control lists',
|
||||||
|
'Connection testing',
|
||||||
|
'User guides'
|
||||||
|
],
|
||||||
|
needs: [
|
||||||
|
'Public IP / DNS details',
|
||||||
|
'User list'
|
||||||
|
],
|
||||||
|
icon: 'vpn_lock',
|
||||||
|
image: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
title: 'Network Infrastructure Support',
|
||||||
|
description: 'Robust network solutions to ensure connectivity, security, and efficiency, including routers, access points, and switches.',
|
||||||
|
challenge: 'A weak network backbone leads to slow speeds, dropped calls, and security holes.',
|
||||||
|
approach: 'We design and maintain enterprise-grade networks that handle your data traffic reliably and securely.',
|
||||||
|
deliverables: [
|
||||||
|
'Router & Switch configuration',
|
||||||
|
'VLAN segmentation',
|
||||||
|
'Wi-Fi optimization',
|
||||||
|
'Network monitoring setup',
|
||||||
|
'Cable management'
|
||||||
|
],
|
||||||
|
needs: [
|
||||||
|
'Floor plans (for Wi-Fi)',
|
||||||
|
'ISP details'
|
||||||
|
],
|
||||||
|
icon: 'lan',
|
||||||
|
image: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
title: 'Network Attached Storage',
|
||||||
|
description: 'Selection, setup, and maintenance of Network Attached Storage solutions to provide scalable and reliable data storage.',
|
||||||
|
challenge: 'Data growth requires scalable storage that is accessible yet secure from loss.',
|
||||||
|
approach: 'We deploy NAS solutions that centralize your data with redundancy and easy access for your team.',
|
||||||
|
deliverables: [
|
||||||
|
'NAS hardware selection & setup',
|
||||||
|
'RAID configuration',
|
||||||
|
'User permission management',
|
||||||
|
'Remote access configuration',
|
||||||
|
'Backup integration'
|
||||||
|
],
|
||||||
|
needs: [
|
||||||
|
'Capacity requirements',
|
||||||
|
'Access patterns'
|
||||||
|
],
|
||||||
|
icon: 'storage',
|
||||||
|
image: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const ServiceModal: React.FC<{ service: typeof services[0] | null; onClose: () => void }> = ({ service, onClose }) => {
|
||||||
|
if (!service) return null;
|
||||||
|
|
||||||
|
// ESC key & Body Scroll Lock
|
||||||
|
useEffect(() => {
|
||||||
|
const handleEsc = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lock scroll on both html and body to prevent background scrolling
|
||||||
|
const originalHtmlOverflow = document.documentElement.style.overflow;
|
||||||
|
const originalBodyOverflow = document.body.style.overflow;
|
||||||
|
|
||||||
|
document.documentElement.style.overflow = 'hidden';
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleEsc);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleEsc);
|
||||||
|
document.documentElement.style.overflow = originalHtmlOverflow;
|
||||||
|
document.body.style.overflow = originalBodyOverflow;
|
||||||
|
};
|
||||||
|
}, [onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-md"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
exit={{ scale: 0.9, opacity: 0 }}
|
||||||
|
className="bg-[#1a1a1a] border border-white/10 rounded-2xl w-full max-w-4xl max-h-[90vh] overflow-y-auto relative shadow-2xl custom-scrollbar"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* Close Button - Sticky and distinct */}
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="fixed md:absolute top-6 right-6 p-2 text-white bg-black/60 hover:bg-white/20 backdrop-blur-sm rounded-full transition-all z-50 group border border-white/10 hover:border-white/40 shadow-lg"
|
||||||
|
aria-label="Close modal"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined group-hover:scale-110 transition-transform">close</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Hero Image in Modal */}
|
||||||
|
{service.image && (
|
||||||
|
<div className="w-full h-64 relative">
|
||||||
|
<img
|
||||||
|
src={service.image}
|
||||||
|
alt={service.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-[#1a1a1a] to-transparent"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="p-8 md:p-12 relative">
|
||||||
|
<div className="flex items-center gap-6 mb-8">
|
||||||
|
<div className="w-16 h-16 bg-white/5 border border-white/10 rounded-2xl flex items-center justify-center shrink-0">
|
||||||
|
<span className="material-symbols-outlined text-3xl text-white">{service.icon}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl font-bold text-white mb-2">{service.title}</h2>
|
||||||
|
<p className="text-gray-400 text-lg">{service.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* ... rest of content */}
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-12 mb-12">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-bold uppercase tracking-wider text-xs mb-4 flex items-center gap-2">
|
||||||
|
<span className="material-symbols-outlined text-sm">warning</span> The Challenge
|
||||||
|
</h4>
|
||||||
|
<p className="text-gray-300 leading-relaxed text-sm md:text-base">{service.challenge}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-bold uppercase tracking-wider text-xs mb-4 flex items-center gap-2">
|
||||||
|
<span className="material-symbols-outlined text-sm">lightbulb</span> Our Approach
|
||||||
|
</h4>
|
||||||
|
<p className="text-gray-300 leading-relaxed text-sm md:text-base">{service.approach}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-8">
|
||||||
|
<h4 className="font-bold text-white mb-6 flex items-center gap-2">
|
||||||
|
<span className="material-symbols-outlined">check_circle</span>
|
||||||
|
What We Deliver
|
||||||
|
</h4>
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
{service.deliverables.map((item, i) => (
|
||||||
|
<div key={i} className="flex items-start gap-3 p-3 rounded-xl bg-white/5 border border-white/5 hover:border-white/20 transition-colors">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-white mt-2 flex-shrink-0 shadow-[0_0_8px_rgba(255,255,255,0.5)]"></span>
|
||||||
|
<span className="text-gray-300 text-sm">{item}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-8 border-t border-white/10">
|
||||||
|
<h4 className="font-semibold text-white mb-3 text-sm uppercase tracking-wide opacity-80">What we need from you</h4>
|
||||||
|
<ul className="text-sm text-gray-400 space-y-2">
|
||||||
|
{service.needs.map((item, i) => (
|
||||||
|
<li key={i} className="flex items-center gap-2">
|
||||||
|
<span className="w-1 h-1 rounded-full bg-white/50"></span>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ServicesPage: React.FC = () => {
|
||||||
|
const [selectedService, setSelectedService] = useState<typeof services[0] | null>(null);
|
||||||
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
const { scrollYProgress } = useScroll({
|
||||||
|
target: containerRef,
|
||||||
|
offset: ["start end", "end center"]
|
||||||
|
});
|
||||||
|
|
||||||
|
const heightTransform = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
|
||||||
|
|
||||||
|
const [activeTimelineIndex, setActiveTimelineIndex] = useState(0);
|
||||||
|
|
||||||
|
useMotionValueEvent(scrollYProgress, "change", (latest) => {
|
||||||
|
// Calculate index based on scroll progress (0-1)
|
||||||
|
// Adjust multiplier to trigger slightly earlier or exact matches
|
||||||
|
const index = Math.min(Math.floor((latest) * services.length), services.length - 1);
|
||||||
|
setActiveTimelineIndex(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-20 min-h-screen bg-[#0a0a0a] relative overflow-x-hidden">
|
||||||
|
{/* Gradient for Services Page */}
|
||||||
|
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.15),rgba(255,255,255,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Hero */}
|
||||||
|
<section className="py-20 px-6 bg-transparent border-b border-white/5 relative z-10">
|
||||||
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
|
<span className="text-white/60 font-bold tracking-widest uppercase text-sm mb-3 block">Expertise</span>
|
||||||
|
<h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-white">
|
||||||
|
Complete IT solutions for <br /><span className="text-gray-500">your business</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-gray-400 max-w-2xl mx-auto">
|
||||||
|
From desktop support to enterprise infrastructure, we provide the technology foundation your business needs to thrive.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Timeline Section */}
|
||||||
|
<section ref={containerRef} className="py-24 px-6 relative overflow-hidden bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
|
||||||
|
<div className="max-w-7xl mx-auto relative z-10">
|
||||||
|
{/* Central Timeline Line */}
|
||||||
|
<div className="absolute left-4 md:left-1/2 md:-ml-[0.5px] top-0 bottom-0 w-px bg-white/5 -translate-x-1/2 md:translate-x-0"></div>
|
||||||
|
|
||||||
|
{/* Active Timeline Line (Scroll Driven) */}
|
||||||
|
<motion.div
|
||||||
|
style={{ height: heightTransform }}
|
||||||
|
className="absolute left-4 md:left-1/2 md:-ml-[0.5px] top-0 w-px bg-white origin-top shadow-[0_0_40px_2px_rgba(255,255,255,1)] drop-shadow-[0_0_10px_rgba(255,255,255,1)] -translate-x-1/2 md:translate-x-0"
|
||||||
|
></motion.div>
|
||||||
|
|
||||||
|
<div className="space-y-64 pb-64">
|
||||||
|
{services.map((service, index) => (
|
||||||
|
<div
|
||||||
|
key={service.id}
|
||||||
|
className={`relative flex flex-col md:flex-row gap-8 md:gap-0 ${index % 2 === 0 ? 'md:flex-row' : 'md:flex-row-reverse'}`}
|
||||||
|
>
|
||||||
|
{/* Timeline Dot */}
|
||||||
|
<motion.div
|
||||||
|
animate={index <= activeTimelineIndex ? {
|
||||||
|
scale: 1.2,
|
||||||
|
opacity: 1,
|
||||||
|
backgroundColor: "#ffffff",
|
||||||
|
borderColor: "#ffffff",
|
||||||
|
boxShadow: "0 0 30px rgba(255,255,255,1)"
|
||||||
|
} : {
|
||||||
|
scale: 0.5,
|
||||||
|
opacity: 0.2,
|
||||||
|
backgroundColor: "#0a0a0a",
|
||||||
|
borderColor: "rgba(255,255,255,0.1)",
|
||||||
|
boxShadow: "none"
|
||||||
|
}}
|
||||||
|
viewport={{ margin: "-50% 0px -50% 0px" }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className="absolute left-4 md:left-1/2 md:-ml-[6px] -translate-x-1/2 md:translate-x-0 w-3 h-3 rounded-full border border-white/20 z-20 top-1/2 -translate-y-1/2 shrink-0"
|
||||||
|
></motion.div>
|
||||||
|
|
||||||
|
{/* Content Card */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.9, filter: "blur(10px)" }}
|
||||||
|
whileInView={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
|
||||||
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
|
transition={{ duration: 0.5, ease: "easeOut" }}
|
||||||
|
className={`md:w-1/2 ${index % 2 === 0 ? 'md:pr-24 pl-12' : 'md:pl-24 pl-12'}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={() => setSelectedService(service)}
|
||||||
|
className="group cursor-pointer bg-white/5 backdrop-blur-sm rounded-2xl p-10 border border-white/10 hover:border-white/30 hover:bg-white/10 transition-all duration-300 relative overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className="w-14 h-14 bg-white/5 rounded-xl flex items-center justify-center mb-8 border border-white/10 group-hover:scale-110 transition-transform duration-500">
|
||||||
|
<span className="material-symbols-outlined text-3xl text-white">{service.icon}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-3xl font-bold text-white mb-4">{service.title}</h3>
|
||||||
|
<p className="text-gray-400 mb-8 line-clamp-3 text-lg leading-relaxed">{service.description}</p>
|
||||||
|
|
||||||
|
<div className="flex items-center text-sm font-bold text-white uppercase tracking-wider group-hover:gap-2 transition-all">
|
||||||
|
See Details <span className="material-symbols-outlined text-sm ml-1">arrow_forward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Spacer for the other side */}
|
||||||
|
<div className="md:w-1/2"></div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Contact />
|
||||||
|
|
||||||
|
{/* Modal */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{selectedService && (
|
||||||
|
<ServiceModal service={selectedService} onClose={() => setSelectedService(null)} />
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesPage;
|
||||||
|
|
@ -6,8 +6,14 @@ export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, '.', '');
|
const env = loadEnv(mode, '.', '');
|
||||||
return {
|
return {
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3012,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
hmr: {
|
||||||
|
clientPort: 3012,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
usePolling: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
define: {
|
define: {
|
||||||
|
|
|
||||||