Pushing project code

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

BIN
src/assets/hero-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

246
src/pages/AboutPage.tsx Normal file
View File

@@ -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;

104
src/pages/BlogPage.tsx Normal file
View File

@@ -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;

207
src/pages/ContactPage.tsx Normal file
View File

@@ -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;

28
src/pages/HomePage.tsx Normal file
View File

@@ -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;

414
src/pages/ServicesPage.tsx Normal file
View File

@@ -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;