Complete SEO overhaul

This commit is contained in:
2026-01-22 15:17:20 +01:00
parent e5c5503e08
commit 42e0971a13
37 changed files with 4319 additions and 1217 deletions

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { locationData } from '../src/data/seoData';
const AreasWeServe: React.FC = () => {
return (
<section className="py-24 px-6 bg-gray-50 dark:bg-white/5 mx-auto text-center border-t border-gray-200 dark:border-white/10">
<div className="max-w-4xl mx-auto">
<h2 className="font-display text-3xl md:text-4xl font-bold mb-6 text-gray-900 dark:text-white">
Areas We Serve Local IT Support Across the Coastal Bend
</h2>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-12 leading-relaxed">
We provide professional IT support and IT services for businesses throughout Corpus Christi and the surrounding Coastal Bend area.
Our team supports local companies with business IT support, outsourced IT services, and help desk solutions, delivered remotely or on-site when needed.
</p>
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-12">
{locationData.map((loc) => (
<Link
key={loc.slug}
to={`/${loc.slug}`}
className="p-4 bg-white dark:bg-white/10 rounded-lg shadow-sm hover:shadow-md transition-all text-gray-800 dark:text-gray-200 font-medium hover:text-black dark:hover:text-white"
>
{loc.city}
</Link>
))}
</div>
<p className="text-lg text-gray-600 dark:text-gray-400">
Not sure if your location is covered? <Link to="/contact" className="underline hover:text-black dark:hover:text-white transition-colors">Contact us today</Link> to discuss your IT needs.
</p>
<div className="mt-6">
<Link to="/it-support-corpus-christi" className="text-sm text-gray-500 hover:text-gray-800 dark:hover:text-gray-300 transition-colors">
Get local IT support in Corpus Christi and nearby areas
</Link>
</div>
</div>
</section>
);
};
export default AreasWeServe;

View File

@@ -1,74 +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;
import React from 'react';
import { motion } from 'framer-motion';
import { Link } from 'react-router-dom';
const CTA: React.FC = () => {
return (
<section className="py-24 px-6 bg-white dark:bg-[#0f0f0f] border-t border-gray-100 dark:border-white/5 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
<div className="max-w-4xl mx-auto text-center">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="font-display text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white"
>
Ready for <span className="text-gray-400 dark:text-gray-500">reliable IT?</span>
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-xl text-gray-600 dark:text-gray-300 mb-12 leading-relaxed"
>
Join 150+ Coastal Bend businesses that trust us with their technology. Get started with a free 20-minute assessment.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
>
<Link
to="/contact"
className="px-8 py-4 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium transition-all hover:scale-105 shadow-lg w-full sm:w-auto"
>
Book a 20-minute assessment
</Link>
<Link
to="/contact"
className="px-8 py-4 bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-white rounded-full font-medium transition-all hover:bg-gray-200 dark:hover:bg-white/20 w-full sm:w-auto"
>
Send a message
</Link>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.3 }}
className="mt-16 grid md:grid-cols-3 gap-8 text-left"
>
<div className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10">
<h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">How quickly can you start?</h3>
<p className="text-gray-600 dark:text-gray-400 text-sm">Most assessments can begin within 48 hours of contact.</p>
</div>
<div className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10">
<h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">How do you price services?</h3>
<p className="text-gray-600 dark:text-gray-400 text-sm">Transparent monthly pricing based on devices and services needed.</p>
</div>
<div className="p-6 rounded-2xl bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10">
<h3 className="font-bold text-lg mb-2 text-gray-900 dark:text-white">What's included in support?</h3>
<p className="text-gray-600 dark:text-gray-400 text-sm">24/7 monitoring, helpdesk, proactive maintenance, and SLA guarantees.</p>
</div>
</motion.div>
</div>
</section>
);
};
export default CTA;

View File

@@ -1,28 +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;
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;

65
components/FAQ.tsx Normal file
View File

@@ -0,0 +1,65 @@
import React from 'react';
import { motion } from 'framer-motion';
import { FAQItem } from '../src/data/seoData';
interface FAQProps {
items: FAQItem[];
}
const FAQ: React.FC<FAQProps> = ({ items }) => {
if (!items || items.length === 0) return null;
return (
<section className="py-24 px-6 bg-white dark:bg-black">
<div className="max-w-3xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<h2 className="font-display text-3xl md:text-4xl font-bold mb-4 text-gray-900 dark:text-white">
Frequently Asked Questions
</h2>
<p className="text-gray-600 dark:text-gray-400">
Common questions about our IT services.
</p>
</motion.div>
<div className="space-y-6">
{items.map((faq, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
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">{faq.question}</h3>
<p className="text-gray-600 dark:text-gray-300">{faq.answer}</p>
</motion.div>
))}
</div>
</div>
{/* JSON-LD for FAQ Page */}
<script type="application/ld+json" dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": items.map(faq => ({
"@type": "Question",
"name": faq.question,
"acceptedAnswer": {
"@type": "Answer",
"text": faq.answer
}
}))
})
}} />
</section>
);
};
export default FAQ;

68
components/SEO.tsx Normal file
View File

@@ -0,0 +1,68 @@
import React, { useEffect } from 'react';
interface SEOProps {
title: string;
description: string;
keywords?: string[];
canonicalUrl?: string;
schema?: object; // JSON-LD schema
}
const SEO: React.FC<SEOProps> = ({ title, description, keywords, canonicalUrl, schema }) => {
useEffect(() => {
// Update Title
document.title = title;
// Helper to set meta tag
const setMetaTag = (name: string, content: string) => {
let element = document.querySelector(`meta[name="${name}"]`);
if (!element) {
element = document.createElement('meta');
element.setAttribute('name', name);
document.head.appendChild(element);
}
element.setAttribute('content', content);
};
// Update Meta Description
setMetaTag('description', description);
// Update Keywords
if (keywords && keywords.length > 0) {
setMetaTag('keywords', keywords.join(', '));
}
// Update Canonical
if (canonicalUrl) {
let link = document.querySelector('link[rel="canonical"]');
if (!link) {
link = document.createElement('link');
link.setAttribute('rel', 'canonical');
document.head.appendChild(link);
}
link.setAttribute('href', canonicalUrl);
}
// Inject Schema
if (schema) {
const scriptId = 'seo-schema-script';
let script = document.getElementById(scriptId);
if (!script) {
script = document.createElement('script');
script.id = scriptId;
script.setAttribute('type', 'application/ld+json');
document.head.appendChild(script);
}
script.textContent = JSON.stringify(schema);
}
// Cleanup function not strictly necessary for single page app navigation
// unless we want to remove specific tags on unmount, but usually we just overwrite them.
}, [title, description, keywords, canonicalUrl, schema]);
return null;
};
export default SEO;

View File

@@ -1,4 +1,4 @@
import React, { useState, useRef, useLayoutEffect } from 'react';
import React, { useState, useRef, useLayoutEffect, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -69,45 +69,68 @@ const servicesData = [
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'
},
{
id: 9,
category: 'IT Infrastructure',
title: 'Business IT Support',
description: 'Comprehensive IT support for businesses, including help desk, maintenance, and strategic planning.',
icon: 'business_center',
image: '/assets/services/business-it.png'
},
{
id: 10,
category: 'IT Infrastructure',
title: 'IT Help Desk',
description: 'Fast and reliable help desk support for employees, resolving technical issues remotely or on-site.',
icon: 'support_agent',
image: '/assets/services/help-desk.png'
},
{
id: 11,
category: 'IT Infrastructure',
title: 'Managed IT Services',
description: 'Proactive monitoring, security, and management of your entire IT infrastructure for a fixed monthly fee.',
icon: 'admin_panel_settings',
image: '/assets/services/managed-it.png'
}
];
const categories = ['All', 'IT Infrastructure', 'Web Services', 'Security', 'Networking'];
const Services: React.FC<{ preview?: boolean }> = ({ preview = false }) => {
interface ServicesProps {
preview?: boolean;
featuredIds?: number[];
}
const Services: React.FC<ServicesProps> = ({ preview = false, featuredIds }) => {
const [activeCategory, setActiveCategory] = useState('All');
const [showAll, setShowAll] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const imagesRef = useRef<(HTMLDivElement | null)[]>([]);
// Reset refs on render to handle filtering updates
imagesRef.current = [];
// Determine if we should be in "preview mode" (showing only a subset)
// This applies if preview is true OR if featuredIds are provided and we haven't clicked "Show More"
const isRestrictedView = (preview || featuredIds) && !showAll;
const filteredServices = activeCategory === 'All'
// Filter services based on category first (unless in restricted view with specific IDs, where we might want to ignore category or just show the specific ones)
const filteredByCategory = activeCategory === 'All'
? servicesData
: servicesData.filter(s => s.category === activeCategory || (activeCategory === 'Web Development' && s.category === 'Security'));
const displayedServices = preview ? servicesData.slice(0, 3) : filteredServices;
useLayoutEffect(() => {
const ctx = gsap.context(() => {
imagesRef.current.forEach((imgWrapper) => {
if (!imgWrapper) return;
gsap.to(imgWrapper, {
yPercent: 30,
ease: "none",
scrollTrigger: {
trigger: imgWrapper.closest('.group'),
start: "top bottom",
end: "bottom top",
scrub: true
}
});
});
}, containerRef);
return () => ctx.revert();
}, [filteredServices]);
const displayedServices = useMemo(() => {
if (isRestrictedView) {
if (featuredIds && featuredIds.length > 0) {
// Sort the services to match the order of featuredIds
return featuredIds
.map(id => servicesData.find(s => s.id === id))
.filter((s): s is typeof servicesData[0] => s !== undefined);
}
// Fallback to first 3 if no IDs but preview is true
return servicesData.slice(0, 3);
}
// Show all (filtered by category)
return filteredByCategory;
}, [isRestrictedView, featuredIds, filteredByCategory]);
return (
<motion.section
@@ -127,32 +150,35 @@ const Services: React.FC<{ preview?: boolean }> = ({ preview = false }) => {
</h2>
</div>
<div className="flex gap-6 mb-12 border-b border-gray-200 dark:border-white/10 text-sm font-medium overflow-x-auto pb-2 no-scrollbar">
{categories.map((cat) => (
<button
key={cat}
onClick={() => setActiveCategory(cat)}
className={`pb-2 whitespace-nowrap transition-colors relative ${activeCategory === cat
? 'text-gray-900 dark:text-white'
: 'text-gray-500 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-300'
}`}
>
{cat}
{activeCategory === cat && (
<motion.div
layoutId="activeTab"
className="absolute bottom-0 left-0 right-0 h-0.5 bg-black dark:bg-white"
/>
)}
</button>
))}
</div>
{/* Categories - Hide in restricted view to keep it clean, or keep it? User said "mach nur das 3 services angezeigt werden". usually categories are for the full list. */}
{!isRestrictedView && (
<div className="flex gap-6 mb-12 border-b border-gray-200 dark:border-white/10 text-sm font-medium overflow-x-auto pb-2 no-scrollbar">
{categories.map((cat) => (
<button
key={cat}
onClick={() => setActiveCategory(cat)}
className={`pb-2 whitespace-nowrap transition-colors relative ${activeCategory === cat
? 'text-gray-900 dark:text-white'
: 'text-gray-500 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-300'
}`}
>
{cat}
{activeCategory === cat && (
<motion.div
layoutId="activeTab"
className="absolute bottom-0 left-0 right-0 h-0.5 bg-black dark:bg-white"
/>
)}
</button>
))}
</div>
)}
<div
className="grid grid-cols-1 md:grid-cols-3 gap-6"
>
<AnimatePresence mode="popLayout">
{filteredServices.map((service, index) => (
{displayedServices.map((service) => (
<motion.div
key={service.id}
layout
@@ -164,34 +190,28 @@ const Services: React.FC<{ preview?: boolean }> = ({ preview = false }) => {
className="group relative bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl overflow-hidden hover:border-gray-300 dark:hover:border-white/30 hover:shadow-2xl transition-all duration-300"
>
{/* Image Container */}
<div className="h-64 bg-gray-200 dark:bg-black/40 overflow-hidden relative">
{/* Parallax Wrapper */}
<div
ref={el => { if (el) imagesRef.current.push(el); }}
className="w-full h-[140%] -mt-[20%]"
>
<img
src={service.image}
alt={service.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110 opacity-100"
/>
</div>
<div className="h-40 bg-gray-200 dark:bg-black/40 overflow-hidden relative">
<img
src={service.image}
alt={service.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110 opacity-100"
/>
<div className="absolute inset-0 bg-gradient-to-t from-gray-50 dark:from-[#161616] to-transparent pointer-events-none"></div>
</div>
<div className="p-6 relative">
<div className="p-4 relative">
<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-8 h-8 rounded-full bg-white dark:bg-white/10 flex items-center justify-center mb-3 border border-gray-200 dark:border-white/10"
whileHover={{ rotate: 360, backgroundColor: "#171717", color: "#ffffff", borderColor: "#171717" }}
transition={{ duration: 0.5 }}
>
<span className="material-symbols-outlined text-sm text-gray-900 dark:text-white group-hover:text-white">{service.icon}</span>
</motion.div>
<h3 className="font-display text-xl font-bold text-gray-900 dark:text-white mb-2">{service.title}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed mb-4">
<h3 className="font-display text-lg font-bold text-gray-900 dark:text-white mb-2">{service.title}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed mb-3">
{service.description}
</p>
<a href="#" className="inline-flex items-center text-xs font-bold uppercase tracking-wide text-gray-900 dark:text-white group-hover:text-gray-600 dark:group-hover:text-gray-300 transition-colors">
<a href="/services" className="inline-flex items-center text-xs font-bold uppercase tracking-wide text-gray-900 dark:text-white group-hover:text-gray-600 dark:group-hover:text-gray-300 transition-colors">
Learn More <motion.span
className="material-symbols-outlined text-xs ml-1"
animate={{ x: [0, 5, 0] }}
@@ -204,16 +224,18 @@ const Services: React.FC<{ preview?: boolean }> = ({ preview = false }) => {
</AnimatePresence>
</div>
{preview && (
{isRestrictedView && (
<div className="mt-12 text-center">
<a
href="/services"
<button
onClick={() => setShowAll(true)}
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>
Show More Services <span className="material-symbols-outlined text-sm">expand_more</span>
</button>
</div>
)}
{/* If we are showing all and originally had a restricted view, maybe show a "Show Less" but user didn't ask for it. The user said "then all are shown". */}
</div>
</motion.section>
);

View File

@@ -1,44 +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;
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;