first commit

This commit is contained in:
Andres Santana
2026-09-30 10:08:56 -04:00
commit ed5e152359
162 changed files with 27775 additions and 0 deletions
@@ -0,0 +1,39 @@
import React, { useEffect, useState, useRef } from 'react';
import { motion, useInView, useSpring, useReducedMotion } from 'framer-motion';
const AnimatedCounter = ({ from = 0, to, duration = 2, className = "", prefix = "", suffix = "" }) => {
const ref = useRef(null);
const inView = useInView(ref, { once: true, margin: "-10%" });
const shouldReduceMotion = useReducedMotion();
const [displayValue, setDisplayValue] = useState(shouldReduceMotion ? to : from);
const springValue = useSpring(from, {
damping: 50,
stiffness: 100,
duration: duration * 1000
});
useEffect(() => {
if (shouldReduceMotion) return;
if (inView) {
springValue.set(to);
}
}, [inView, to, springValue, shouldReduceMotion]);
useEffect(() => {
if (shouldReduceMotion) return;
return springValue.on("change", (latest) => {
setDisplayValue(Math.floor(latest));
});
}, [springValue, shouldReduceMotion]);
return (
<span ref={ref} className={className}>
{prefix}{displayValue}{suffix}
</span>
);
};
export default AnimatedCounter;
+48
View File
@@ -0,0 +1,48 @@
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import { ImageOff } from 'lucide-react';
const BrandedImage = ({
src,
alt,
className = '',
imgClassName = '',
overlayOpacity = 0.4,
lazy = true
}) => {
const [error, setError] = useState(false);
const [loaded, setLoaded] = useState(false);
if (error || !src) {
return (
<div className={`relative flex items-center justify-center bg-secondary text-muted-foreground overflow-hidden ${className}`}>
<ImageOff className="w-12 h-12 opacity-20" />
</div>
);
}
return (
<div className={`relative overflow-hidden group ${className}`}>
<motion.img
initial={{ scale: 1.05, filter: 'blur(10px)' }}
animate={loaded ? { scale: 1, filter: 'blur(0px)' } : {}}
transition={{ duration: 1.2, ease: "easeOut" }}
src={src}
alt={alt}
loading={lazy ? "lazy" : "eager"}
onLoad={() => setLoaded(true)}
onError={() => setError(true)}
className={`w-full h-full object-cover parallax-slow ${imgClassName}`}
/>
{/* Premium Red Duotone/Overlay Effect */}
<div
className="absolute inset-0 bg-primary mix-blend-multiply transition-opacity duration-500 group-hover:opacity-80"
style={{ opacity: overlayOpacity }}
/>
{/* Subtle vignette for depth */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-black/20 pointer-events-none" />
</div>
);
};
export default BrandedImage;
+56
View File
@@ -0,0 +1,56 @@
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { ChevronRight, Home } from 'lucide-react';
const Breadcrumbs = () => {
const location = useLocation();
const pathnames = location.pathname.split('/').filter((x) => x);
// Custom formatting for breadcrumb labels
const formatLabel = (path) => {
const specialCases = {
'paid-media': 'Paid Media',
'seo': 'SEO',
'llmo': 'LLMO/AEO/GEO',
'web-development': 'Web Development',
'social-media': 'Social Media',
'brand': 'Brand',
'consulting': 'Consulting',
'cybersecurity': 'Cybersecurity'
};
if (specialCases[path]) return specialCases[path];
return path.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
};
return (
<nav className="flex items-center space-x-1 text-sm text-muted-foreground mb-8">
<Link to="/" className="hover:text-primary transition-colors flex items-center">
<Home className="h-4 w-4" />
<span className="sr-only">Home</span>
</Link>
{pathnames.map((value, index) => {
const to = `/${pathnames.slice(0, index + 1).join('/')}`;
const isLast = index === pathnames.length - 1;
return (
<React.Fragment key={to}>
<ChevronRight className="h-4 w-4" />
{isLast ? (
<span className="text-foreground font-medium" aria-current="page">
{formatLabel(value)}
</span>
) : (
<Link to={to} className="hover:text-primary transition-colors">
{formatLabel(value)}
</Link>
)}
</React.Fragment>
);
})}
</nav>
);
};
export default Breadcrumbs;
@@ -0,0 +1,43 @@
import React from 'react';
import { motion } from 'framer-motion';
import { CheckCircle2 } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
const CapabilitiesTable = ({ title, capabilities }) => {
return (
<section className="service-section bg-secondary/30">
<div className="mb-12 text-center md:text-left">
<h2 className="service-heading">{title}</h2>
<div className="w-20 h-1 bg-primary mb-6 mx-auto md:mx-0"></div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{capabilities.map((capability, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1, duration: 0.5 }}
>
<Card className="h-full bg-card border-border hover:border-primary/50 transition-colors">
<CardHeader className="pb-3 flex flex-row items-center gap-4 space-y-0">
<CheckCircle2 className="w-6 h-6 text-primary flex-shrink-0" />
<CardTitle className="text-lg text-heading">{capability.title || capability}</CardTitle>
</CardHeader>
{capability.description && (
<CardContent>
<p className="text-muted text-sm leading-relaxed">
{capability.description}
</p>
</CardContent>
)}
</Card>
</motion.div>
))}
</div>
</section>
);
};
export default CapabilitiesTable;
+222
View File
@@ -0,0 +1,222 @@
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { useToast } from '@/components/ui/use-toast';
import { Phone, Loader2 } from 'lucide-react';
const ContactForm = () => {
const { toast } = useToast();
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
fullName: '',
companyName: '',
phoneNumber: '',
email: '',
hqAddress: '',
services: [],
hasAgency: '',
});
const dubaiPhoneNumber = "+971 55 647 0342";
const servicesList = [
'Web Development',
'SEO',
'LLMO & GEO',
'Social Media Management',
'Branding',
'Paid Advertising',
];
const handleServiceChange = (service) => {
setFormData((prev) => {
const services = prev.services.includes(service)
? prev.services.filter((s) => s !== service)
: [...prev.services, service];
return { ...prev, services };
});
};
const handleSubmit = async (e) => {
e.preventDefault();
const requiredFields = [
formData.fullName,
formData.companyName,
formData.phoneNumber,
formData.email,
formData.hqAddress,
formData.hasAgency
];
if (requiredFields.some(field => !field) || formData.services.length === 0) {
toast({
title: "Missing Information",
description: "Please fill out all required fields marked with *",
variant: "destructive"
});
return;
}
setIsSubmitting(true);
try {
await new Promise(r => setTimeout(r, 1500));
toast({
title: "Your message has been sent successfully! 🚀",
description: "We'll analyze your requirements and get back to you shortly.",
duration: 5000,
className: "bg-green-600 border-green-700 text-white",
});
setFormData({
fullName: '', companyName: '', phoneNumber: '', email: '', hqAddress: '', services: [], hasAgency: '',
});
} catch (error) {
toast({
title: "Failed to send message",
description: "Please try again later or contact us directly via phone.",
variant: "destructive"
});
} finally {
setIsSubmitting(false);
}
};
return (
<section id="contact-form" className="py-20 bg-background relative">
<div className="container mx-auto px-4 max-w-3xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="bg-card border border-border rounded-2xl p-8 md:p-12 shadow-2xl backdrop-blur-sm"
>
<div className="text-center mb-10">
<h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">Start Your Growth Journey</h2>
<p className="text-muted-foreground">Tell us about your goals and we'll help you achieve them.</p>
<div className="mt-6 flex items-center justify-center text-primary">
<Phone className="mr-2" size={20} />
<a href={`tel:${dubaiPhoneNumber.replace(/[\s-]/g, '')}`} className="text-lg font-semibold hover:underline">
{dubaiPhoneNumber}
</a>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-8">
<div className="grid md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="fullName" className="text-foreground">Full Name <span className="text-red-500">*</span></Label>
<Input
id="fullName" required disabled={isSubmitting}
value={formData.fullName} onChange={(e) => setFormData({ ...formData, fullName: e.target.value })}
className="bg-background border-border text-foreground focus:ring-primary disabled:opacity-50"
placeholder="John Doe"
/>
</div>
<div className="space-y-2">
<Label htmlFor="companyName" className="text-foreground">Company Name <span className="text-red-500">*</span></Label>
<Input
id="companyName" required disabled={isSubmitting}
value={formData.companyName} onChange={(e) => setFormData({ ...formData, companyName: e.target.value })}
className="bg-background border-border text-foreground focus:ring-primary disabled:opacity-50"
placeholder="Acme Corp"
/>
</div>
</div>
<div className="grid md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="phoneNumber" className="text-foreground">Phone Number <span className="text-red-500">*</span></Label>
<Input
id="phoneNumber" required type="tel" disabled={isSubmitting}
value={formData.phoneNumber} onChange={(e) => setFormData({ ...formData, phoneNumber: e.target.value })}
className="bg-background border-border text-foreground focus:ring-primary disabled:opacity-50"
placeholder="+1 (555) 000-0000"
/>
</div>
<div className="space-y-2">
<Label htmlFor="email" className="text-foreground">Email Address <span className="text-red-500">*</span></Label>
<Input
id="email" required type="email" disabled={isSubmitting}
value={formData.email} onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="bg-background border-border text-foreground focus:ring-primary disabled:opacity-50"
placeholder="your@email.com"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="hqAddress" className="text-foreground">HQ Address <span className="text-red-500">*</span></Label>
<Input
id="hqAddress" required disabled={isSubmitting}
value={formData.hqAddress} onChange={(e) => setFormData({ ...formData, hqAddress: e.target.value })}
className="bg-background border-border text-foreground focus:ring-primary disabled:opacity-50"
placeholder="123 Business Ave, Suite 100, City, Country"
/>
</div>
<div className="space-y-4">
<Label className="text-foreground block text-base">Services Needed (Select all that apply) <span className="text-red-500">*</span></Label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{servicesList.map((service) => (
<div key={service} className="flex items-center space-x-3 p-3 rounded-lg border border-border bg-secondary hover:border-primary/50 transition-colors">
<Checkbox
id={service} disabled={isSubmitting}
checked={formData.services.includes(service)}
onCheckedChange={() => handleServiceChange(service)}
className="border-border data-[state=checked]:bg-primary data-[state=checked]:border-primary disabled:opacity-50"
/>
<Label htmlFor={service} className="text-foreground cursor-pointer font-normal">
{service}
</Label>
</div>
))}
</div>
</div>
<div className="space-y-4">
<Label className="text-foreground block text-base">Currently working with another agency? <span className="text-red-500">*</span></Label>
<RadioGroup
value={formData.hasAgency}
onValueChange={(val) => setFormData({ ...formData, hasAgency: val })}
className="flex flex-col space-y-2" required disabled={isSubmitting}
>
<div className="flex items-center space-x-3 p-3 rounded-lg border border-border bg-secondary">
<RadioGroupItem value="yes" id="agency-yes" className="border-primary text-primary disabled:opacity-50" />
<Label htmlFor="agency-yes" className="text-foreground cursor-pointer">Yes</Label>
</div>
<div className="flex items-center space-x-3 p-3 rounded-lg border border-border bg-secondary">
<RadioGroupItem value="no" id="agency-no" className="border-primary text-primary disabled:opacity-50" />
<Label htmlFor="agency-no" className="text-foreground cursor-pointer">No</Label>
</div>
</RadioGroup>
</div>
<Button
type="submit" disabled={isSubmitting}
className="w-full bg-primary hover:bg-primary/90 text-primary-foreground py-6 text-lg rounded-full shadow-lg shadow-primary/25 disabled:opacity-70 disabled:cursor-not-allowed"
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Sending Message...
</>
) : (
'Submit Request'
)}
</Button>
</form>
</motion.div>
</div>
</section>
);
};
export default ContactForm;
+41
View File
@@ -0,0 +1,41 @@
import React from 'react';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { motion } from 'framer-motion';
const FAQSection = ({ faqs }) => {
return (
<section className="service-section max-w-4xl mx-auto">
<div className="text-center mb-12">
<h2 className="service-heading">Frequently Asked Questions</h2>
<div className="w-20 h-1 bg-primary mx-auto mb-6"></div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<Accordion type="single" collapsible className="w-full">
{faqs.map((faq, index) => (
<AccordionItem key={index} value={`item-${index}`} className="border-border">
<AccordionTrigger className="text-left text-lg text-heading hover:text-primary font-medium px-4">
{faq.question}
</AccordionTrigger>
<AccordionContent className="text-muted px-4 leading-relaxed text-base">
{faq.answer}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</motion.div>
</section>
);
};
export default FAQSection;
+143
View File
@@ -0,0 +1,143 @@
import React from 'react';
import { motion } from 'framer-motion';
import { Mail, Phone, MapPin, Linkedin, Facebook, Instagram } from 'lucide-react';
import { Link } from 'react-router-dom';
import ScrollReveal from './ScrollReveal';
const Footer = () => {
const socialLinks = [
{ icon: Instagram, url: 'https://www.instagram.com/aigrowthmediaservice/', label: 'Instagram' },
{ icon: Facebook, url: 'https://www.facebook.com/growth360partners', label: 'Facebook' },
{ icon: Linkedin, url: 'https://www.linkedin.com/company/growth360partners', label: 'LinkedIn' }
];
const quickLinks = [
{ label: 'About Us', href: '/about' },
{ label: 'Our Team', href: '/our-team' },
{ label: 'Portfolio', href: '/portfolio' },
{ label: 'Contact', href: '/contact' }
];
const servicesLinks = [
{ label: 'Services Hub', href: '/services' },
{ label: 'Paid Media', href: '/services/paid-media' },
{ label: 'SEO', href: '/services/seo' },
{ label: 'LLMO/AEO/GEO', href: '/services/llmo' },
{ label: 'Web Development', href: '/services/web-development' },
{ label: 'Social Media', href: '/services/social-media' },
{ label: 'Brand Creation', href: '/services/brand' },
{ label: 'Cybersecurity', href: '/services/cybersecurity' }
];
return (
<footer className="bg-black border-t border-white/10 relative overflow-hidden text-white pt-20 pb-10">
{/* Subtle Background Glows */}
<div className="absolute top-0 right-0 w-[700px] h-[700px] glow-red rounded-full filter blur-[120px] -translate-y-1/2 translate-x-1/3 pointer-events-none" />
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] glow-blue rounded-full filter blur-[100px] translate-y-1/3 -translate-x-1/3 pointer-events-none" />
<div className="container mx-auto px-4 max-w-7xl relative z-10">
<ScrollReveal className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-10 lg:gap-8 mb-14">
{/* Company Info */}
<div className="flex flex-col lg:col-span-4 pr-4">
<Link to="/" className="mb-6 block">
<img src="https://horizons-cdn.hostinger.com/cbcecfe5-8f3e-47c5-bf6c-fcb30d4288f5/35787cd5f7025697b247b5601494d350.png" alt="AIGROWTH Media Service logo" className="h-14 md:h-16 w-auto object-contain" />
</Link>
<p className="text-white/70 mb-8 leading-relaxed text-sm md:text-base pr-8">
Your growth engine for digital success. We combine strategy, technology, and creativity to deliver results that compound over time.
</p>
<div className="flex gap-3">
{socialLinks.map((social, index) => (
<motion.a
key={index} href={social.url} aria-label={social.label}
whileHover={{ scale: 1.1, y: -2 }}
className="w-10 h-10 rounded-full bg-white/10 border border-white/15 flex items-center justify-center text-white/70 hover:bg-primary hover:text-white hover:border-primary transition-all duration-300"
>
<social.icon size={18} />
</motion.a>
))}
</div>
</div>
{/* Links */}
<div className="lg:col-span-2">
<h4 className="text-white font-bold text-base mb-5 tracking-tight">Company</h4>
<nav className="flex flex-col gap-3">
{quickLinks.map((link, index) => (
<Link key={index} to={link.href} className="text-white/70 hover:text-primary transition-colors duration-300 text-sm">
{link.label}
</Link>
))}
</nav>
</div>
<div className="lg:col-span-3">
<h4 className="text-white font-bold text-base mb-5 tracking-tight">Capabilities</h4>
<nav className="flex flex-col gap-3">
{servicesLinks.map((link, index) => (
<Link key={index} to={link.href} className="text-white/70 hover:text-primary transition-colors duration-300 text-sm">
{link.label}
</Link>
))}
</nav>
</div>
{/* Contact Info */}
<div className="lg:col-span-3">
<h4 className="text-white font-bold text-base mb-5 tracking-tight">Contact</h4>
<div className="flex flex-col gap-5 text-sm text-white/70">
<div className="flex items-start gap-3 group">
<MapPin size={18} className="mt-0.5 flex-shrink-0 text-primary group-hover:scale-110 transition-transform" />
<p>Business Bay, Dubai, UAE<br/>(Headquarters)</p>
</div>
<div className="flex items-start gap-3 group">
<Phone size={18} className="mt-0.5 flex-shrink-0 text-primary group-hover:scale-110 transition-transform" />
<div className="flex flex-col gap-2">
<div>
<span className="text-[10px] uppercase tracking-widest text-white/45 font-bold block mb-1">Dubai</span>
<a href="tel:+971556470342" className="hover:text-primary transition-colors">+971 55 647 0342</a>
</div>
<div>
<span className="text-[10px] uppercase tracking-widest text-white/45 font-bold block mb-1 mt-2">USA Support</span>
<a href="tel:+18323500465" className="block hover:text-primary transition-colors mb-1">+1 832-350-0465</a>
<a href="tel:+19542354974" className="block hover:text-primary transition-colors mb-1">+1 954-235-4974</a>
<a href="tel:+17132894577" className="block hover:text-primary transition-colors">+1 713-289-4577</a>
</div>
</div>
</div>
<div className="flex items-center gap-3 group">
<Mail size={18} className="flex-shrink-0 text-primary group-hover:scale-110 transition-transform" />
<a href="mailto:info@aigrowthtechnologies.com" className="hover:text-primary transition-colors truncate">
info@aigrowthtechnologies.com
</a>
</div>
</div>
</div>
</ScrollReveal>
{/* Partners & Bottom Bar */}
<ScrollReveal delay={0.2}>
<div className="border-t border-white/10 pt-8 flex flex-col md:flex-row justify-between items-center gap-8">
<div className="flex items-center gap-5 opacity-60 hover:opacity-100 transition-all duration-500">
<span className="text-[10px] font-black tracking-widest uppercase text-white/50">Powered By</span>
<img src="https://horizons-cdn.hostinger.com/cbcecfe5-8f3e-47c5-bf6c-fcb30d4288f5/a3b73073fbeb071d0e115b026561212b.png" alt="meSEO" className="h-4" />
<img src="https://horizons-cdn.hostinger.com/cbcecfe5-8f3e-47c5-bf6c-fcb30d4288f5/fb9e42499289adaa8ac7e4f3fb56d72f.png" alt="Korrith" className="h-7" />
<img src="https://horizons-cdn.hostinger.com/cbcecfe5-8f3e-47c5-bf6c-fcb30d4288f5/9d06cdfd1608625e82935667ceb9db7d.png" alt="Atrilyx" className="h-5" />
</div>
<div className="flex flex-col md:flex-row items-center gap-4 text-xs text-white/60">
<p>© {new Date().getFullYear()} AI Growth Technologies. All rights reserved.</p>
<div className="flex gap-4">
<a href="#" className="hover:text-primary transition-colors">Privacy</a>
<a href="#" className="hover:text-primary transition-colors">Terms</a>
</div>
</div>
</div>
</ScrollReveal>
</div>
</footer>
);
};
export default Footer;
+205
View File
@@ -0,0 +1,205 @@
import React, { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Menu, X, ChevronDown, ArrowRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Link, useLocation, useNavigate } from 'react-router-dom';
const Header = () => {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isAboutOpen, setIsAboutOpen] = useState(false);
const [isServicesOpen, setIsServicesOpen] = useState(false);
const location = useLocation();
const navigate = useNavigate();
const dropdownRef = useRef(null);
const servicesDropdownRef = useRef(null);
useEffect(() => {
const handleScroll = () => setIsScrolled(window.scrollY > 20);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) setIsAboutOpen(false);
if (servicesDropdownRef.current && !servicesDropdownRef.current.contains(event.target)) setIsServicesOpen(false);
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
useEffect(() => {
setIsAboutOpen(false);
setIsServicesOpen(false);
setIsMobileMenuOpen(false);
}, [location.pathname]);
const isActive = (path) => {
if (path === '/' && location.pathname !== '/') return false;
return location.pathname === path || (path !== '/' && location.pathname.startsWith(path));
};
const isServiceActive = () => location.pathname.startsWith('/services');
const navLinkClass = (active) =>
`transition-colors duration-300 font-semibold text-sm 2xl:text-base ${active ? 'text-primary' : 'text-white/75 hover:text-white'}`;
return (
<motion.header
id="site-header"
initial={{ y: -100 }}
animate={{ y: 0 }}
transition={{ duration: 0.6 }}
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 bg-black text-white ${
isScrolled ? 'shadow-[0_8px_30px_rgba(0,0,0,0.24)] border-b border-white/10' : 'border-b border-transparent'
}`}
>
<nav className="container mx-auto px-4 max-w-7xl">
<div className="flex items-center justify-between h-16 md:h-20">
{/* Logo Group */}
<div className="flex items-center gap-4 shrink-0">
<motion.div whileHover={{ scale: 1.02 }} transition={{ type: 'spring', stiffness: 400 }}>
<Link to="/" className="flex items-center shrink-0 rounded-lg transition-colors">
<img
src="https://horizons-cdn.hostinger.com/cbcecfe5-8f3e-47c5-bf6c-fcb30d4288f5/5d6f7e4aab2acc9623d351a632c92129.png"
alt="AIGROWTH Media Service logo"
className="h-12 md:h-16 w-auto object-contain"
/>
</Link>
</motion.div>
{/* POWERED BY — partner logos remain visible on wide desktop navigation */}
<div className="hidden xl:flex items-center gap-5 border-l border-white/15 pl-5">
<span className="text-[10px] font-black text-white/50 tracking-widest uppercase">Powered By</span>
<img src="https://horizons-cdn.hostinger.com/cbcecfe5-8f3e-47c5-bf6c-fcb30d4288f5/a3b73073fbeb071d0e115b026561212b.png" alt="meSEO Logo" className="h-6 w-auto object-contain opacity-70 hover:opacity-100 transition-opacity duration-300" />
<img src="https://horizons-cdn.hostinger.com/cbcecfe5-8f3e-47c5-bf6c-fcb30d4288f5/fb9e42499289adaa8ac7e4f3fb56d72f.png" alt="Korrith Logo" className="h-10 w-auto object-contain hover:scale-105 transition-transform duration-300 opacity-90 hover:opacity-100" />
<img src="https://horizons-cdn.hostinger.com/cbcecfe5-8f3e-47c5-bf6c-fcb30d4288f5/9d06cdfd1608625e82935667ceb9db7d.png" alt="Atrilyx Logo" className="h-7 w-auto object-contain hover:scale-105 transition-transform duration-300 opacity-70 hover:opacity-100" />
</div>
</div>
{/* Desktop Navigation — visible from lg up (fixes lg→xl gap) */}
<div className="hidden lg:flex items-center gap-5 xl:gap-7 shrink-0">
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }} className="relative" ref={servicesDropdownRef}>
<button
onClick={() => { setIsServicesOpen(!isServicesOpen); setIsAboutOpen(false); }}
className={`flex items-center gap-1 ${navLinkClass(isServiceActive() || isServicesOpen)}`}
>
Services
<ChevronDown className={`w-4 h-4 transition-transform duration-300 ${isServicesOpen ? 'rotate-180' : ''}`} />
</button>
<AnimatePresence>
{isServicesOpen && (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }} animate={{ opacity: 1, y: 0, scale: 1 }} exit={{ opacity: 0, y: 10, scale: 0.95 }} transition={{ duration: 0.2 }}
className="absolute top-full left-1/2 -translate-x-1/2 mt-4 w-[440px] max-w-[calc(100vw-2rem)] bg-white border border-border rounded-2xl shadow-2xl shadow-black/10 overflow-hidden"
>
<div className="p-4 flex flex-col">
<Link to="/services" className={`px-4 py-3 rounded-lg text-sm font-bold transition-all mb-2 ${isActive('/services') && location.pathname === '/services' ? 'bg-primary/10 text-primary' : 'text-foreground hover:bg-secondary hover:text-foreground'}`}>
Services Hub Directory
</Link>
<div className="h-px bg-border mb-3 mx-2" />
<div className="grid grid-cols-2 gap-1">
<div className="flex flex-col gap-1">
<Link to="/services/paid-media" className="px-4 py-2 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">Paid Media</Link>
<Link to="/services/seo" className="px-4 py-2 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">SEO</Link>
<Link to="/services/llmo" className="px-4 py-2 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">LLMO / AEO / GEO</Link>
<Link to="/services/web-development" className="px-4 py-2 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">Web Development</Link>
</div>
<div className="flex flex-col gap-1">
<Link to="/services/social-media" className="px-4 py-2 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">Social Media</Link>
<Link to="/services/brand" className="px-4 py-2 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">Brand Creation</Link>
<Link to="/services/cybersecurity" className="px-4 py-2 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">Cybersecurity</Link>
<Link to="/services/consulting" className="px-4 py-2 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">Consulting</Link>
</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.15 }}>
<Link to="/industries" className={navLinkClass(isActive('/industries'))}>Industries</Link>
</motion.div>
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.2 }}>
<Link to="/portfolio" className={navLinkClass(isActive('/portfolio'))}>Portfolio</Link>
</motion.div>
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.3 }} className="relative" ref={dropdownRef}>
<button
onClick={() => { setIsAboutOpen(!isAboutOpen); setIsServicesOpen(false); }}
className={`flex items-center gap-1 ${navLinkClass(isActive('/about') || isActive('/our-team') || isAboutOpen)}`}
>
About Us
<ChevronDown className={`w-4 h-4 transition-transform duration-300 ${isAboutOpen ? 'rotate-180' : ''}`} />
</button>
<AnimatePresence>
{isAboutOpen && (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }} animate={{ opacity: 1, y: 0, scale: 1 }} exit={{ opacity: 0, y: 10, scale: 0.95 }} transition={{ duration: 0.2 }}
className="absolute top-full left-1/2 -translate-x-1/2 mt-4 w-56 bg-white border border-border rounded-2xl shadow-2xl shadow-black/10 overflow-hidden"
>
<div className="p-2 flex flex-col">
<Link to="/about" className="px-4 py-3 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">Our Story</Link>
<Link to="/our-team" className="px-4 py-3 rounded-lg text-sm font-medium transition-all text-muted-foreground hover:bg-secondary hover:text-foreground">Our Team</Link>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.4 }}>
<Link to="/contact" className={navLinkClass(isActive('/contact'))}>Contact</Link>
</motion.div>
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.5 }}>
<Button onClick={() => navigate('/contact')} className="bg-primary hover:bg-primary/90 text-white px-5 py-2.5 rounded-full shadow-lg shadow-primary/20 transition-all duration-300 font-bold tracking-wide border-0 group">
Start Today <ArrowRight className="ml-1.5 w-4 h-4 group-hover:translate-x-0.5 transition-transform" />
</Button>
</motion.div>
</div>
{/* Mobile Nav Toggle */}
<div className="flex items-center lg:hidden gap-2">
<button onClick={() => navigate('/contact')} className="hidden sm:inline-flex bg-primary text-white px-4 py-2 rounded-full text-sm font-bold shadow-lg shadow-primary/20 transition-colors hover:bg-primary/90">
Start Today
</button>
<button onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} aria-label="Toggle menu" className="p-2 rounded-lg transition-colors duration-300 shrink-0 text-white hover:bg-white/10">
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
</div>
</nav>
{/* Mobile Menu Content */}
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: 'auto' }} exit={{ opacity: 0, height: 0 }} transition={{ duration: 0.3 }}
className="lg:hidden overflow-hidden bg-black border-t border-white/10 shadow-2xl shadow-black/30 relative z-40"
>
<div className="flex flex-col gap-1 p-4">
<Link to="/services" onClick={() => setIsMobileMenuOpen(false)} className="text-base font-bold py-3 px-3 rounded-lg border-b border-white/10 text-white hover:bg-white/10 hover:text-primary">Services</Link>
<Link to="/industries" onClick={() => setIsMobileMenuOpen(false)} className="text-base font-bold py-3 px-3 rounded-lg border-b border-white/10 text-white hover:bg-white/10 hover:text-primary">Industries</Link>
<Link to="/portfolio" onClick={() => setIsMobileMenuOpen(false)} className="text-base font-bold py-3 px-3 rounded-lg border-b border-white/10 text-white hover:bg-white/10 hover:text-primary">Portfolio</Link>
<Link to="/about" onClick={() => setIsMobileMenuOpen(false)} className="text-base font-bold py-3 px-3 rounded-lg border-b border-white/10 text-white hover:bg-white/10 hover:text-primary">About</Link>
<Link to="/our-team" onClick={() => setIsMobileMenuOpen(false)} className="text-base font-bold py-3 px-3 rounded-lg border-b border-white/10 text-white hover:bg-white/10 hover:text-primary">Our Team</Link>
<Link to="/contact" onClick={() => setIsMobileMenuOpen(false)} className="text-base font-bold py-3 px-3 rounded-lg text-white hover:bg-white/10 hover:text-primary">Contact</Link>
<Button onClick={() => { navigate('/contact'); setIsMobileMenuOpen(false); }} className="bg-primary hover:bg-primary/90 text-white w-full rounded-full py-4 mt-4 font-bold shadow-lg shadow-primary/20 border-0">
Start Today
</Button>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.header>
);
};
export default Header;
@@ -0,0 +1,36 @@
import React from 'react';
import { motion } from 'framer-motion';
const HowWeWorkSection = ({ title, steps }) => {
return (
<section className="service-section bg-secondary/30">
<div className="text-center mb-16">
<h2 className="service-heading">{title}</h2>
<div className="w-20 h-1 bg-primary mx-auto"></div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 relative">
<div className="hidden lg:block absolute top-1/4 left-[10%] right-[10%] h-0.5 bg-border -z-10"></div>
{steps.map((step, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.15, duration: 0.5 }}
className="flex flex-col items-center text-center relative group"
>
<div className="w-16 h-16 rounded-full bg-background border-2 border-border group-hover:border-primary flex items-center justify-center text-xl font-bold text-muted-foreground group-hover:text-primary transition-colors mb-6 shadow-lg z-10">
0{index + 1}
</div>
<h3 className="text-xl font-bold mb-3">{step.title}</h3>
<p className="text-muted-foreground text-sm">{step.description}</p>
</motion.div>
))}
</div>
</section>
);
};
export default HowWeWorkSection;
@@ -0,0 +1,48 @@
import React from 'react';
import { motion } from 'framer-motion';
import { Briefcase } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
const IndustriesTable = ({ title, intro, industries }) => {
return (
<section className="service-section bg-secondary/10">
<div className="mb-12 text-center md:text-left">
<h2 className="service-heading">{title}</h2>
<div className="w-20 h-1 bg-primary mb-6 mx-auto md:mx-0"></div>
{intro && (
<p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
{intro}
</p>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{industries.map((industry, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1, duration: 0.5 }}
>
<Card className="h-full bg-card border-border hover:border-primary/50 transition-colors">
<CardHeader className="pb-3 flex flex-row items-center gap-4 space-y-0">
<Briefcase className="w-6 h-6 text-primary flex-shrink-0" />
<CardTitle className="text-lg">{industry.title}</CardTitle>
</CardHeader>
<CardContent>
<ul className="text-muted-foreground text-sm leading-relaxed list-disc list-inside ml-2">
{industry.examples.map((example, i) => (
<li key={i} className="mb-1">{example}</li>
))}
</ul>
</CardContent>
</Card>
</motion.div>
))}
</div>
</section>
);
};
export default IndustriesTable;
+175
View File
@@ -0,0 +1,175 @@
import React from 'react';
import { motion } from 'framer-motion';
import { MapPin, Phone, Globe, Building2, Mail } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Link } from 'react-router-dom';
const Offices = () => {
const offices = [
{
city: 'Dubai',
state: '',
country: 'UAE',
location: 'Downtown Dubai',
address: 'P.O. Box 123456, Dubai',
phone: '+971 55 647 0342',
email: 'info@aigrowthtechnologies.com',
description: 'Our global headquarters and innovation hub driving digital excellence in the MENA region.',
gradient: 'from-red-600 to-orange-600',
type: 'headquarters',
},
{
city: 'Houston',
state: 'TX',
country: 'USA',
location: 'Downtown Houston',
phone: '+1 832-350-0465',
gradient: 'from-cyan-500 to-blue-600',
type: 'partner',
},
{
city: 'Miami',
state: 'FL',
country: 'USA',
location: 'Brickell Avenue',
phone: '+1 954-235-4974',
gradient: 'from-teal-500 to-cyan-600',
type: 'partner',
},
{
city: 'New York City',
state: 'NY',
country: 'USA',
location: 'Manhattan Financial District',
phone: '+1 713-289-4577',
gradient: 'from-blue-500 to-indigo-600',
type: 'partner',
},
];
return (
<section className="py-20 md:py-32 bg-secondary/40 relative overflow-hidden">
<div className="absolute inset-0 bg-tech-dots [mask-image:radial-gradient(ellipse_at_center,black,transparent_75%)] opacity-60 pointer-events-none" />
<div className="absolute top-0 left-0 w-[500px] h-[500px] glow-red rounded-full filter blur-[120px] pointer-events-none" />
<div className="absolute bottom-0 right-0 w-[500px] h-[500px] glow-blue rounded-full filter blur-[120px] pointer-events-none" />
<div className="container mx-auto px-4 relative z-10">
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
className="text-center mb-16"
>
<span className="text-primary font-semibold text-sm uppercase tracking-wider mb-4 block">
Global Presence
</span>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-black text-foreground mb-6">
Our <span className="text-gradient">Global</span> Reach
</h2>
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
Headquartered in Dubai with strategic partner offices across the USA, we're positioned to serve clients worldwide.
</p>
</motion.div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-16">
{offices.map((office, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: index * 0.1 }}
whileHover={{ y: -8 }}
className="group relative h-full"
>
<div className="relative h-full flex flex-col bg-white border border-border rounded-2xl p-6 hover:border-primary/40 hover:shadow-xl hover:shadow-primary/5 transition-all duration-300">
<div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${office.gradient} flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300 shadow-lg`}>
{office.type === 'headquarters' ? <Building2 className="text-white" size={24} /> : <MapPin className="text-white" size={24} />}
</div>
<h3 className="text-xl font-bold text-foreground mb-1">
{office.city}
</h3>
{office.type === 'headquarters' ? (
<span className="inline-block bg-primary/10 text-primary text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded border border-primary/20 mb-3 self-start">Global HQ</span>
) : (
<span className="inline-block bg-secondary text-muted-foreground text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded border border-border mb-3 self-start">Partner Office</span>
)}
{office.description && (
<p className="text-muted-foreground text-xs mb-4 leading-relaxed">
{office.description}
</p>
)}
<div className="space-y-3 mt-auto">
<div className="text-muted-foreground text-xs flex flex-col gap-2">
{office.location && (
<div className="flex items-start gap-2">
<MapPin size={14} className="shrink-0 text-primary mt-0.5" />
<div className="font-medium text-foreground/80">{office.location}</div>
</div>
)}
<div className="flex items-center gap-2 opacity-70 ml-5">
<Globe size={12} />
<span>{office.state ? `${office.state}, ` : ''}{office.country}</span>
</div>
</div>
<div className="flex flex-col gap-2 text-primary text-sm">
<div className="flex items-center gap-2">
<Phone size={14} className="shrink-0" />
<a href={`tel:${office.phone.replace(/[\s-]/g, '')}`} className="font-semibold hover:underline truncate">{office.phone}</a>
</div>
{office.email && (
<div className="flex items-center gap-2">
<Mail size={14} className="shrink-0" />
<a href={`mailto:${office.email}`} className="font-medium hover:underline truncate">{office.email}</a>
</div>
)}
</div>
</div>
<Link to="/contact" className="mt-6">
<Button className="w-full bg-primary hover:bg-primary/90 text-white rounded-full shadow-lg transition-all duration-300 text-xs py-2">
Contact Office
</Button>
</Link>
</div>
</motion.div>
))}
</div>
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
className="text-center"
>
<div className="bg-gradient-to-r from-primary to-red-500 rounded-3xl p-12 md:p-16 shadow-2xl shadow-primary/20 relative overflow-hidden">
<div className="absolute inset-0 bg-tech-dots opacity-20 pointer-events-none" />
<h3 className="text-3xl md:text-4xl lg:text-5xl font-black text-white mb-6 relative">
Ready to Accelerate Your Growth?
</h3>
<p className="text-xl text-white/90 mb-8 max-w-2xl mx-auto relative">
Let's build a custom growth strategy for your business. Schedule a consultation with our global team today.
</p>
<Link to="/contact" className="relative">
<Button
size="lg"
className="bg-white text-primary hover:bg-white/90 px-8 py-6 rounded-full text-lg shadow-xl transition-all duration-300 hover:scale-105 font-bold"
>
Start Today
</Button>
</Link>
</div>
</motion.div>
</div>
</section>
);
};
export default Offices;
@@ -0,0 +1,54 @@
import React from 'react';
import { motion } from 'framer-motion';
import { Settings, Zap, BarChart, Shield, Layout, Globe } from 'lucide-react';
const PlatformSection = ({ title, description, features }) => {
const icons = [Settings, Zap, BarChart, Shield, Layout, Globe];
return (
<section className="service-section">
<div className="flex flex-col lg:flex-row gap-16 items-center">
<motion.div
className="lg:w-1/3"
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="service-heading text-balance">{title}</h2>
<div className="w-20 h-1 bg-primary mb-6"></div>
<p className="service-subheading mb-8">
{description}
</p>
</motion.div>
<motion.div
className="lg:w-2/3 grid grid-cols-1 sm:grid-cols-2 gap-4"
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
{features.map((feature, index) => {
const Icon = icons[index % icons.length];
return (
<div key={index} className="flex items-start gap-4 p-5 rounded-lg border border-border bg-card/50 hover:bg-card transition-colors">
<div className="bg-primary/10 p-3 rounded-md border border-primary/20">
<Icon className="w-5 h-5 text-primary" />
</div>
<div>
<h3 className="font-semibold text-foreground mb-1">{feature.title || feature}</h3>
{feature.description && (
<p className="text-sm text-muted-foreground">{feature.description}</p>
)}
</div>
</div>
);
})}
</motion.div>
</div>
</section>
);
};
export default PlatformSection;
@@ -0,0 +1,12 @@
import React from 'react';
import { Navigate } from 'react-router-dom';
import { isAuthenticated } from '@/lib/auth';
const ProtectedRoute = ({ children }) => {
if (!isAuthenticated()) {
return <Navigate to="/login" replace />;
}
return children;
};
export default ProtectedRoute;
+31
View File
@@ -0,0 +1,31 @@
import React from 'react';
import { motion, useReducedMotion } from 'framer-motion';
const ScrollReveal = ({
children,
className = '',
delay = 0,
duration = 0.6,
yOffset = 40,
once = true
}) => {
const shouldReduceMotion = useReducedMotion();
if (shouldReduceMotion) {
return <div className={className}>{children}</div>;
}
return (
<motion.div
initial={{ opacity: 0, y: yOffset }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once, margin: "-10%" }}
transition={{ duration, delay, ease: [0.16, 1, 0.3, 1] }}
className={className}
>
{children}
</motion.div>
);
};
export default ScrollReveal;
+14
View File
@@ -0,0 +1,14 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
const ScrollToTop = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
};
export default ScrollToTop;
+24
View File
@@ -0,0 +1,24 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { ArrowRight } from 'lucide-react';
const ServiceCTA = ({ text, link = "/contact" }) => {
return (
<section className="py-20 bg-background border-t border-border">
<div className="container mx-auto px-4 text-center">
<div className="max-w-3xl mx-auto bg-secondary/50 rounded-2xl p-10 md:p-16 border border-border">
<h2 className="text-3xl md:text-4xl font-bold mb-8">{text}</h2>
<Link to={link}>
<Button size="lg" className="bg-primary hover:bg-primary/90 text-primary-foreground text-lg px-8 py-6 rounded-full">
Get Started
<ArrowRight className="ml-2 h-5 w-5" />
</Button>
</Link>
</div>
</div>
</section>
);
};
export default ServiceCTA;
+33
View File
@@ -0,0 +1,33 @@
import React from 'react';
import Breadcrumbs from '@/components/Breadcrumbs';
import BrandedImage from '@/components/BrandedImage';
import ScrollReveal from '@/components/ScrollReveal';
const ServiceHero = ({ title, subheading, tagline, image }) => {
return (
<section className="relative pt-32 pb-24 md:pt-40 md:pb-32 overflow-hidden border-b border-border bg-background">
<div className="absolute inset-0 z-0 opacity-[0.03] dark:opacity-10 pointer-events-none">
<BrandedImage src={image} alt="" lazy={false} overlayOpacity={0.8} />
</div>
<div className="container mx-auto px-4 max-w-7xl relative z-10">
<ScrollReveal>
<Breadcrumbs />
<div className="mt-8 mb-4">
<span className="inline-block px-3 py-1 bg-primary/10 text-primary text-xs font-bold uppercase tracking-widest rounded-full">
{tagline}
</span>
</div>
<h1 className="text-4xl md:text-5xl lg:text-7xl font-extrabold tracking-tight mb-6 max-w-4xl text-balance text-heading">
{title}
</h1>
<p className="text-lg md:text-2xl text-muted leading-relaxed max-w-3xl">
{subheading}
</p>
</ScrollReveal>
</div>
</section>
);
};
export default ServiceHero;
+98
View File
@@ -0,0 +1,98 @@
import React from 'react';
import { motion } from 'framer-motion';
import { Link } from 'react-router-dom';
import { servicesData } from '@/lib/servicesData';
import { ArrowRight } from 'lucide-react';
const Services = () => {
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.6 },
},
};
return (
<section className="py-20 md:py-32 bg-slate-950 relative overflow-hidden">
<div className="absolute inset-0 opacity-5">
<div className="absolute top-0 left-0 w-96 h-96 bg-blue-500 rounded-full filter blur-3xl"></div>
<div className="absolute bottom-0 right-0 w-96 h-96 bg-red-500 rounded-full filter blur-3xl"></div>
</div>
<div className="container mx-auto px-4 relative z-10">
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
className="text-center mb-16"
>
<span className="text-blue-400 font-semibold text-sm uppercase tracking-wider mb-4 block">
Our Services
</span>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6">
Full-Spectrum Digital
<br />
<span className="bg-gradient-to-r from-blue-400 to-red-400 bg-clip-text text-transparent">
Marketing Solutions
</span>
</h2>
<p className="text-xl text-slate-300 max-w-3xl mx-auto">
From strategy to execution, we provide end-to-end services that drive measurable growth for your business.
</p>
</motion.div>
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
>
{servicesData.map((service, index) => (
<motion.div
key={index}
variants={itemVariants}
whileHover={{ y: -8, scale: 1.02 }}
className="group relative bg-slate-900/50 backdrop-blur-sm border border-slate-800 rounded-2xl hover:border-blue-500/50 transition-all duration-300 flex flex-col h-full"
>
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-red-500/5 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<Link to={`/services/${service.slug}`} className="relative z-10 p-8 flex flex-col h-full">
<div className={`w-16 h-16 rounded-xl bg-gradient-to-br ${service.gradient} flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300 shadow-lg`}>
<service.icon className="text-white" size={32} />
</div>
<h3 className="text-2xl font-bold text-white mb-4 group-hover:text-blue-400 transition-colors duration-300">
{service.title}
</h3>
<p className="text-slate-400 leading-relaxed mb-6 flex-grow">
{service.description}
</p>
<div className="flex items-center text-blue-400 font-semibold text-sm group-hover:translate-x-2 transition-transform duration-300">
Learn More <ArrowRight size={16} className="ml-2" />
</div>
</Link>
</motion.div>
))}
</motion.div>
</div>
</section>
);
};
export default Services;
+30
View File
@@ -0,0 +1,30 @@
import React, { createContext, useContext, useEffect } from 'react';
const ThemeContext = createContext();
// Light theme only — the site no longer supports a dark mode.
export const ThemeProvider = ({ children }) => {
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove('dark');
root.classList.add('light');
// Clear any previously persisted dark preference.
localStorage.removeItem('theme-preference');
}, []);
// Kept for backward compatibility with consumers that still call useTheme.
// toggleTheme is a no-op now.
return (
<ThemeContext.Provider value={{ theme: 'light', toggleTheme: () => {} }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
+41
View File
@@ -0,0 +1,41 @@
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef(({ className, ...props }, ref) => (
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}>
{children}
<ChevronDown
className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
+34
View File
@@ -0,0 +1,34 @@
import * as React from "react"
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant,
...props
}) {
return (<div className={cn(badgeVariants({ variant }), className)} {...props} />);
}
export { Badge, badgeVariants }
+47
View File
@@ -0,0 +1,47 @@
import { cn } from '@/lib/utils';
import { Slot } from '@radix-ui/react-slot';
import { cva } from 'class-variance-authority';
import React from 'react';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
);
const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
});
Button.displayName = 'Button';
export { Button, buttonVariants };
+50
View File
@@ -0,0 +1,50 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
{...props} />
))
Card.displayName = "Card"
const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props} />
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props} />
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props} />
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props} />
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
+24
View File
@@ -0,0 +1,24 @@
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }
+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
})
Input.displayName = "Input"
export { Input }
+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }
@@ -0,0 +1,35 @@
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem }
+18
View File
@@ -0,0 +1,18 @@
import React from "react"
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 text-foreground",
className
)}
ref={ref}
{...props}
/>
)
})
Textarea.displayName = "Textarea"
export { Textarea }
+101
View File
@@ -0,0 +1,101 @@
import { cn } from '@/lib/utils';
import * as ToastPrimitives from '@radix-ui/react-toast';
import { cva } from 'class-variance-authority';
import { X } from 'lucide-react';
import React from 'react';
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
className,
)}
{...props}
/>
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
'data-[swipe=move]:transition-none group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full',
{
variants: {
variant: {
default: 'bg-background border',
destructive:
'group destructive border-destructive bg-destructive text-destructive-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
);
const Toast = React.forwardRef(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
);
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-destructive/30 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
className,
)}
{...props}
/>
));
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
className,
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn('text-sm font-semibold', className)}
{...props}
/>
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn('text-sm opacity-90', className)}
{...props}
/>
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
export {
Toast,
ToastAction,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
};
+34
View File
@@ -0,0 +1,34 @@
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from '@/components/ui/toast';
import { useToast } from '@/components/ui/use-toast';
import React from 'react';
export function Toaster() {
const { toasts } = useToast();
return (
<ToastProvider>
{toasts.map(({ id, title, description, action, ...props }) => {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
);
})}
<ToastViewport />
</ToastProvider>
);
}
+103
View File
@@ -0,0 +1,103 @@
import { useState, useEffect } from "react"
const TOAST_LIMIT = 1
let count = 0
function generateId() {
count = (count + 1) % Number.MAX_VALUE
return count.toString()
}
const toastStore = {
state: {
toasts: [],
},
listeners: [],
getState: () => toastStore.state,
setState: (nextState) => {
if (typeof nextState === 'function') {
toastStore.state = nextState(toastStore.state)
} else {
toastStore.state = { ...toastStore.state, ...nextState }
}
toastStore.listeners.forEach(listener => listener(toastStore.state))
},
subscribe: (listener) => {
toastStore.listeners.push(listener)
return () => {
toastStore.listeners = toastStore.listeners.filter(l => l !== listener)
}
}
}
export const toast = ({ ...props }) => {
const id = generateId()
const update = (props) =>
toastStore.setState((state) => ({
...state,
toasts: state.toasts.map((t) =>
t.id === id ? { ...t, ...props } : t
),
}))
const dismiss = () => toastStore.setState((state) => ({
...state,
toasts: state.toasts.filter((t) => t.id !== id),
}))
toastStore.setState((state) => ({
...state,
toasts: [
{ ...props, id, dismiss },
...state.toasts,
].slice(0, TOAST_LIMIT),
}))
return {
id,
dismiss,
update,
}
}
export function useToast() {
const [state, setState] = useState(toastStore.getState())
useEffect(() => {
const unsubscribe = toastStore.subscribe((state) => {
setState(state)
})
return unsubscribe
}, [])
useEffect(() => {
const timeouts = []
state.toasts.forEach((toast) => {
if (toast.duration === Infinity) {
return
}
const timeout = setTimeout(() => {
toast.dismiss()
}, toast.duration || 5000)
timeouts.push(timeout)
})
return () => {
timeouts.forEach((timeout) => clearTimeout(timeout))
}
}, [state.toasts])
return {
toast,
toasts: state.toasts,
}
}