Add Results in Numbers, Service Selector, and Popular Bundles to Services page
- Metrics section: 4 gradient stat cards with spring entrance animation - Interactive selector: 3-step wizard (goal → budget → recommendations) - Popular Bundles: 3 package tiers (Starter, Growth, Full Stack) with CTA - Full responsive support for all new sections Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a0cec68ff2
commit
9d281920e9
2 changed files with 670 additions and 3 deletions
|
|
@ -274,6 +274,401 @@
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Results in Numbers ── */
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1.5rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 20px;
|
||||
padding: 2rem 1.5rem;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.3s, box-shadow 0.4s, transform 0.35s;
|
||||
}
|
||||
|
||||
.metric-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: radial-gradient(circle at 30% 20%, rgba(255, 91, 4, 0.08), transparent 60%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s;
|
||||
pointer-events: none;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.metric-card:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.metric-card:hover::before { opacity: 1; }
|
||||
|
||||
.metric-value {
|
||||
font-size: clamp(2rem, 4vw, 2.8rem);
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
background: linear-gradient(135deg, var(--orange-100), var(--yellow-100));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
color: var(--light-grey-100);
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.85;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ── Service Selector ── */
|
||||
.selector-section .container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.selector-steps {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.selector-step-dot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
opacity: 0.35;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.selector-step-dot--active { opacity: 1; }
|
||||
|
||||
.selector-step-num {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid rgba(255, 255, 255, 0.15);
|
||||
color: var(--light-grey-100);
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.selector-step-dot--active .selector-step-num {
|
||||
border-color: var(--orange-100);
|
||||
background: var(--orange-100);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.selector-step-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--light-grey-100);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.selector-panel {
|
||||
text-align: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.selector-question {
|
||||
font-size: 1.15rem;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.selector-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.selector-pill {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 100px;
|
||||
padding: 0.75rem 1.5rem;
|
||||
color: var(--light-grey-100);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-primary);
|
||||
transition: border-color 0.3s, box-shadow 0.3s, background 0.3s;
|
||||
}
|
||||
|
||||
.selector-pill:hover {
|
||||
border-color: rgba(255, 91, 4, 0.4);
|
||||
background: rgba(255, 91, 4, 0.08);
|
||||
}
|
||||
|
||||
.selector-pill--active {
|
||||
border-color: var(--orange-100);
|
||||
background: rgba(255, 91, 4, 0.12);
|
||||
color: var(--orange-100);
|
||||
box-shadow: 0 0 20px rgba(255, 91, 4, 0.15);
|
||||
}
|
||||
|
||||
.selector-results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
|
||||
.selector-result-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
padding: 1rem 1.25rem;
|
||||
text-align: left;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.selector-result-card:hover {
|
||||
border-color: rgba(255, 91, 4, 0.3);
|
||||
}
|
||||
|
||||
.selector-result-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
color: var(--orange-100);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.selector-result-icon svg { width: 100%; height: 100%; }
|
||||
|
||||
.selector-result-card h4 {
|
||||
color: #fff;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.selector-result-price {
|
||||
color: var(--orange-100);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.selector-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.selector-reset {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--light-grey-100);
|
||||
opacity: 0.7;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-primary);
|
||||
text-decoration: underline;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.selector-reset:hover { opacity: 1; }
|
||||
|
||||
.selector-back {
|
||||
display: block;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--light-grey-100);
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-primary);
|
||||
margin: 1rem auto 0;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.selector-back:hover { opacity: 1; }
|
||||
|
||||
.selector-no-results {
|
||||
color: var(--light-grey-100);
|
||||
opacity: 0.7;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* ── Popular Bundles ── */
|
||||
.bundles-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1.5rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.bundle-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.3s, box-shadow 0.4s, transform 0.35s;
|
||||
}
|
||||
|
||||
.bundle-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: radial-gradient(circle at 50% 0%, rgba(255, 91, 4, 0.06), transparent 60%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bundle-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.bundle-card:hover::before { opacity: 1; }
|
||||
|
||||
.bundle-card--popular {
|
||||
border-color: var(--orange-100);
|
||||
background: rgba(255, 91, 4, 0.06);
|
||||
}
|
||||
|
||||
.bundle-card--popular::before { opacity: 0.5; }
|
||||
|
||||
.bundle-card--popular:hover {
|
||||
box-shadow: 0 8px 40px rgba(255, 91, 4, 0.15);
|
||||
}
|
||||
|
||||
.bundle-popular-badge {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(135deg, var(--orange-100), var(--yellow-100));
|
||||
color: #fff;
|
||||
padding: 0.3rem 1rem;
|
||||
border-radius: 100px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 2px 10px rgba(255, 91, 4, 0.3);
|
||||
}
|
||||
|
||||
.bundle-name {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.bundle-tagline {
|
||||
font-size: 0.9rem;
|
||||
color: var(--light-grey-100);
|
||||
opacity: 0.75;
|
||||
margin-bottom: 1.25rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.bundle-services {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 1.5rem 0;
|
||||
flex: 1;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.bundle-services li {
|
||||
color: var(--light-grey-100);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
padding: 0.35rem 0 0.35rem 1.4rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bundle-services li::before {
|
||||
content: '→';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--yellow-100);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bundle-price {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 900;
|
||||
background: linear-gradient(135deg, var(--orange-100), var(--yellow-100));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.bundle-cta {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
background: rgba(255, 91, 4, 0.15);
|
||||
color: var(--orange-100);
|
||||
border: 1px solid rgba(255, 91, 4, 0.3);
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 100px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-primary);
|
||||
transition: background 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.bundle-cta:hover {
|
||||
background: rgba(255, 91, 4, 0.25);
|
||||
}
|
||||
|
||||
.bundle-cta--popular {
|
||||
background: var(--orange-100);
|
||||
color: #fff;
|
||||
border-color: var(--orange-100);
|
||||
box-shadow: 0 4px 15px rgba(255, 91, 4, 0.3);
|
||||
}
|
||||
|
||||
.bundle-cta--popular:hover {
|
||||
box-shadow: 0 8px 25px rgba(255, 91, 4, 0.45);
|
||||
}
|
||||
|
||||
/* Scroll margin for selector "View Services" */
|
||||
.services-grid {
|
||||
scroll-margin-top: 10rem;
|
||||
}
|
||||
|
||||
/* CTA */
|
||||
.services-cta-section {
|
||||
padding-bottom: 6rem;
|
||||
|
|
@ -342,6 +737,15 @@
|
|||
.services-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.bundles-grid {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
|
|
@ -368,6 +772,35 @@
|
|||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.selector-steps {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.selector-step-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selector-pill {
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.bundles-grid {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.services-cta {
|
||||
padding: 2.5rem 1.5rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import SEO from '../components/SEO';
|
||||
import Modal from '../components/Modal';
|
||||
|
|
@ -173,10 +173,87 @@ const assurancePack = [
|
|||
'Handover & transition plan',
|
||||
];
|
||||
|
||||
const metrics = [
|
||||
{ value: '30–50%', label: 'Reduction in manual work' },
|
||||
{ value: '2–8 wks', label: 'Average delivery time' },
|
||||
{ value: '24/7', label: 'Automated uptime' },
|
||||
{ value: '£0', label: 'Vendor lock-in fees' },
|
||||
];
|
||||
|
||||
type GoalKey = 'automate' | 'leads' | 'presence' | 'connect' | 'ai';
|
||||
type BudgetKey = 'under5k' | '5k-10k' | '10k+';
|
||||
|
||||
const goalOptions: { key: GoalKey; label: string }[] = [
|
||||
{ key: 'automate', label: 'Automate workflows' },
|
||||
{ key: 'leads', label: 'Get more leads' },
|
||||
{ key: 'presence', label: 'Build online presence' },
|
||||
{ key: 'connect', label: 'Connect my tools' },
|
||||
{ key: 'ai', label: 'Add AI capabilities' },
|
||||
];
|
||||
|
||||
const budgetOptions: { key: BudgetKey; label: string }[] = [
|
||||
{ key: 'under5k', label: 'Under £5K' },
|
||||
{ key: '5k-10k', label: '£5K – £10K' },
|
||||
{ key: '10k+', label: '£10K+' },
|
||||
];
|
||||
|
||||
const goalToServices: Record<GoalKey, string[]> = {
|
||||
automate: ['Workflow Automation Implementation', 'System Integration & Synchronisation'],
|
||||
leads: ['AI Chatbots & Virtual Assistants', 'CRM Workflow Optimisation', 'Marketing Automation Setup'],
|
||||
presence: ['Custom Website Development'],
|
||||
connect: ['System Integration & Synchronisation', 'Infrastructure Setup & Configuration'],
|
||||
ai: ['AI Integration & Enhancement', 'AI Chatbots & Virtual Assistants'],
|
||||
};
|
||||
|
||||
const budgetFilter: Record<BudgetKey, string[]> = {
|
||||
'under5k': [
|
||||
'Infrastructure Setup & Configuration', 'CRM Workflow Optimisation',
|
||||
'Custom Website Development', 'System Integration & Synchronisation',
|
||||
'AI Chatbots & Virtual Assistants',
|
||||
],
|
||||
'5k-10k': [
|
||||
'Infrastructure Setup & Configuration', 'CRM Workflow Optimisation',
|
||||
'Custom Website Development', 'System Integration & Synchronisation',
|
||||
'AI Chatbots & Virtual Assistants', 'Workflow Automation Implementation',
|
||||
'Marketing Automation Setup', 'AI Integration & Enhancement',
|
||||
],
|
||||
'10k+': services.map(s => s.title),
|
||||
};
|
||||
|
||||
const bundles = [
|
||||
{
|
||||
name: 'Starter',
|
||||
tagline: 'Launch & Engage',
|
||||
services: ['Custom Website Development', 'AI Chatbots & Virtual Assistants'],
|
||||
price: 'from £5,500',
|
||||
},
|
||||
{
|
||||
name: 'Growth',
|
||||
tagline: 'Scale & Convert',
|
||||
services: ['CRM Workflow Optimisation', 'Marketing Automation Setup', 'System Integration & Synchronisation'],
|
||||
price: 'from £9,000',
|
||||
popular: true,
|
||||
},
|
||||
{
|
||||
name: 'Full Stack',
|
||||
tagline: 'Transform Everything',
|
||||
services: services.map(s => s.title),
|
||||
price: 'Custom pricing',
|
||||
},
|
||||
];
|
||||
|
||||
const ServicesPage = () => {
|
||||
useEffect(() => { window.scrollTo(0, 0); }, []);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [expandedCard, setExpandedCard] = useState<number | null>(null);
|
||||
const [selectorStep, setSelectorStep] = useState(0);
|
||||
const [selectedGoal, setSelectedGoal] = useState<GoalKey | null>(null);
|
||||
const [selectedBudget, setSelectedBudget] = useState<BudgetKey | null>(null);
|
||||
const serviceCardsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const recommendedServices = selectedGoal && selectedBudget
|
||||
? goalToServices[selectedGoal].filter(s => budgetFilter[selectedBudget].includes(s))
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div className="services-page">
|
||||
|
|
@ -225,7 +302,7 @@ const ServicesPage = () => {
|
|||
{/* Service Cards */}
|
||||
<section className="services-section">
|
||||
<div className="container">
|
||||
<div className="services-grid">
|
||||
<div className="services-grid" ref={serviceCardsRef}>
|
||||
{services.map((s, i) => {
|
||||
const isExpanded = expandedCard === i;
|
||||
return (
|
||||
|
|
@ -299,6 +376,163 @@ const ServicesPage = () => {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{/* Results in Numbers */}
|
||||
<section className="services-section">
|
||||
<div className="container">
|
||||
<motion.h2 className="section-title" {...fadeUp}>Results in Numbers</motion.h2>
|
||||
<div className="metrics-grid">
|
||||
{metrics.map((m, i) => (
|
||||
<motion.div
|
||||
key={m.label}
|
||||
className="metric-card"
|
||||
initial={{ opacity: 0, scale: 0.5 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ type: 'spring', stiffness: 200, damping: 15, delay: i * 0.1 }}
|
||||
>
|
||||
<span className="metric-value">{m.value}</span>
|
||||
<span className="metric-label">{m.label}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Which Service Do You Need? */}
|
||||
<section className="services-section selector-section">
|
||||
<div className="container">
|
||||
<motion.h2 className="section-title" {...fadeUp}>Which Service Do You Need?</motion.h2>
|
||||
|
||||
<div className="selector-steps">
|
||||
{['Your Goal', 'Budget', 'Results'].map((label, i) => (
|
||||
<div key={label} className={`selector-step-dot ${selectorStep >= i ? 'selector-step-dot--active' : ''}`}>
|
||||
<span className="selector-step-num">{i + 1}</span>
|
||||
<span className="selector-step-label">{label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
{selectorStep === 0 && (
|
||||
<motion.div key="goal" className="selector-panel" initial={{ opacity: 0, x: 40 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -40 }} transition={{ duration: 0.3 }}>
|
||||
<p className="selector-question">What's your primary goal?</p>
|
||||
<div className="selector-options">
|
||||
{goalOptions.map((opt) => (
|
||||
<motion.button
|
||||
key={opt.key}
|
||||
className={`selector-pill ${selectedGoal === opt.key ? 'selector-pill--active' : ''}`}
|
||||
onClick={() => { setSelectedGoal(opt.key); setSelectorStep(1); }}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
>
|
||||
{opt.label}
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{selectorStep === 1 && (
|
||||
<motion.div key="budget" className="selector-panel" initial={{ opacity: 0, x: 40 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -40 }} transition={{ duration: 0.3 }}>
|
||||
<p className="selector-question">What's your budget range?</p>
|
||||
<div className="selector-options">
|
||||
{budgetOptions.map((opt) => (
|
||||
<motion.button
|
||||
key={opt.key}
|
||||
className={`selector-pill ${selectedBudget === opt.key ? 'selector-pill--active' : ''}`}
|
||||
onClick={() => { setSelectedBudget(opt.key); setSelectorStep(2); }}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
>
|
||||
{opt.label}
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{selectorStep === 2 && (
|
||||
<motion.div key="results" className="selector-panel" initial={{ opacity: 0, x: 40 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -40 }} transition={{ duration: 0.3 }}>
|
||||
<p className="selector-question">We recommend these services:</p>
|
||||
<div className="selector-results">
|
||||
{recommendedServices.length > 0 ? recommendedServices.map((sTitle) => {
|
||||
const svc = services.find(s => s.title === sTitle);
|
||||
if (!svc) return null;
|
||||
return (
|
||||
<div key={sTitle} className="selector-result-card">
|
||||
<div className="selector-result-icon">{svc.icon}</div>
|
||||
<div>
|
||||
<h4>{svc.title}</h4>
|
||||
<span className="selector-result-price">{svc.price}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}) : (
|
||||
<p className="selector-no-results">No exact match — but we can help! Contact us for a custom solution.</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="selector-actions">
|
||||
<motion.button
|
||||
className="services-cta-btn"
|
||||
onClick={() => serviceCardsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
>
|
||||
View Services
|
||||
</motion.button>
|
||||
<button className="selector-reset" onClick={() => { setSelectorStep(0); setSelectedGoal(null); setSelectedBudget(null); }}>
|
||||
Start Over
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{selectorStep > 0 && selectorStep < 2 && (
|
||||
<button className="selector-back" onClick={() => setSelectorStep(s => s - 1)}>
|
||||
← Back
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Popular Bundles */}
|
||||
<section className="services-section">
|
||||
<div className="container">
|
||||
<motion.h2 className="section-title" {...fadeUp}>Popular Bundles</motion.h2>
|
||||
<div className="bundles-grid">
|
||||
{bundles.map((b, i) => (
|
||||
<motion.div
|
||||
key={b.name}
|
||||
className={`bundle-card ${'popular' in b && b.popular ? 'bundle-card--popular' : ''}`}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.5, delay: i * 0.1 }}
|
||||
>
|
||||
{'popular' in b && b.popular && <span className="bundle-popular-badge">Most Popular</span>}
|
||||
<h3 className="bundle-name">{b.name}</h3>
|
||||
<p className="bundle-tagline">{b.tagline}</p>
|
||||
<ul className="bundle-services">
|
||||
{b.services.map(s => (
|
||||
<li key={s}>{s}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="bundle-price">{b.price}</div>
|
||||
<motion.button
|
||||
className={`bundle-cta ${'popular' in b && b.popular ? 'bundle-cta--popular' : ''}`}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
>
|
||||
Get Started
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="services-section services-cta-section">
|
||||
<div className="container">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue