feat(engagement): add Interactive Custom Studio Mood Board, 5-Star Client Reviews showcase, and smart booking prefill
This commit is contained in:
@@ -0,0 +1,464 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
interface PaletteOption {
|
||||
id: string;
|
||||
name: string;
|
||||
colors: string[];
|
||||
desc: string;
|
||||
}
|
||||
|
||||
interface EventTypeOption {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
interface ElementOption {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
const eventTypes: EventTypeOption[] = [
|
||||
{ id: 'wedding', label: 'Wedding & Engagement', icon: '💍' },
|
||||
{ id: 'milestone_birthday', label: 'Milestone Birthday (30th/50th)', icon: '🎂' },
|
||||
{ id: 'baby_shower', label: 'Baby Shower / Gender Reveal', icon: '👶' },
|
||||
{ id: 'themed_party', label: 'Themed / 1st Birthday', icon: '🍓' },
|
||||
{ id: 'corporate_gala', label: 'Corporate Gala & Brand Launch', icon: '🥂' },
|
||||
];
|
||||
|
||||
const palettes: PaletteOption[] = [
|
||||
{
|
||||
id: 'royal_noir',
|
||||
name: 'Royal Noir Gala',
|
||||
colors: ['#000000', '#D4AF37', '#990000'],
|
||||
desc: 'Onyx black marble, reflective chrome gold, and deep crimson.',
|
||||
},
|
||||
{
|
||||
id: 'sweet_berry',
|
||||
name: 'Berry Sweet Pastel',
|
||||
colors: ['#E0006F', '#FFB6C1', '#002244'],
|
||||
desc: 'Strawberry red, blush baby pink, and deep midnight navy.',
|
||||
},
|
||||
{
|
||||
id: 'tuscan_garden',
|
||||
name: 'Tuscan Sunshine & Flora',
|
||||
colors: ['#F5C518', '#FFFFFF', '#4A5D4E'],
|
||||
desc: 'Warm sun yellow, crisp bridal white, and silk eucalyptus greenery.',
|
||||
},
|
||||
{
|
||||
id: 'tropical_neon',
|
||||
name: 'Tropical Luxe Brights',
|
||||
colors: ['#FF007F', '#FF6600', '#00F0FF'],
|
||||
desc: 'Vibrant hot pink, electric orange, and bright turquoise.',
|
||||
},
|
||||
{
|
||||
id: 'celestial_moon',
|
||||
name: 'Midnight & Gold Moon',
|
||||
colors: ['#0B1B3D', '#C8A2C8', '#D4AF37'],
|
||||
desc: 'Royal navy, soft lavender drape, and gold starburst accents.',
|
||||
},
|
||||
];
|
||||
|
||||
const decorElements: ElementOption[] = [
|
||||
{ id: 'balloon_cascade', label: 'Organic Balloon Cloud Cascade', icon: '🎈', tag: 'Most Popular' },
|
||||
{ id: 'arch_panels', label: 'Custom Multi-Arch Wood Panels', icon: '🏛️', tag: 'Signature' },
|
||||
{ id: 'mosaic_letters', label: 'Giant 3D Balloon Mosaic Letters', icon: '🔤', tag: 'High Impact' },
|
||||
{ id: 'beverage_station', label: 'Arched Drink & Treat Station', icon: '🍸', tag: 'Interactive' },
|
||||
{ id: 'floral_accents', label: 'Silk Florals & Botanical Sprigs', icon: '🌹', tag: 'Luxury' },
|
||||
{ id: 'custom_lettering', label: '3D Gold Script Custom Name Sign', icon: '✨', tag: 'Personalized' },
|
||||
];
|
||||
|
||||
export default function MoodBoardEstimator() {
|
||||
const [selectedEventType, setSelectedEventType] = useState<string>('milestone_birthday');
|
||||
const [selectedPalette, setSelectedPalette] = useState<string>('royal_noir');
|
||||
const [selectedElements, setSelectedElements] = useState<string[]>([
|
||||
'balloon_cascade',
|
||||
'arch_panels',
|
||||
'custom_lettering',
|
||||
]);
|
||||
|
||||
const toggleElement = (id: string) => {
|
||||
setSelectedElements((prev) =>
|
||||
prev.includes(id) ? prev.filter((el) => el !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
const currentEventType = eventTypes.find((e) => e.id === selectedEventType)?.label || '';
|
||||
const currentPalette = palettes.find((p) => p.id === selectedPalette);
|
||||
const selectedElementLabels = decorElements
|
||||
.filter((e) => selectedElements.includes(e.id))
|
||||
.map((e) => e.label);
|
||||
|
||||
const handleApplyToBooking = () => {
|
||||
const visionString = `Custom Design Studio Selection:
|
||||
• Celebration: ${currentEventType}
|
||||
• Color Palette: ${currentPalette?.name} (${currentPalette?.desc})
|
||||
• Core Decor Elements: ${selectedElementLabels.join(', ')}`;
|
||||
|
||||
// Dispatch custom event to prefill booking form
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('prefillWiseHeartedBooking', {
|
||||
detail: {
|
||||
eventType: currentEventType,
|
||||
vision: visionString,
|
||||
services: selectedElementLabels,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Smoothly scroll to contact form
|
||||
const contactSection = document.getElementById('contact');
|
||||
if (contactSection) {
|
||||
contactSection.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
id="custom-studio"
|
||||
style={{
|
||||
padding: 'clamp(70px, 10vw, 110px) 0',
|
||||
backgroundColor: '#050505',
|
||||
position: 'relative',
|
||||
borderBottom: '1px solid var(--border-gold)',
|
||||
}}
|
||||
>
|
||||
<div className="container" style={{ maxWidth: '1120px' }}>
|
||||
{/* Section Header */}
|
||||
<div style={{ textAlign: 'center', marginBottom: '45px', padding: '0 8px' }}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '0.85rem',
|
||||
fontWeight: 800,
|
||||
color: 'var(--color-pink-light)',
|
||||
letterSpacing: '0.15em',
|
||||
textTransform: 'uppercase',
|
||||
display: 'block',
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
>
|
||||
🎨 Interactive Custom Studio
|
||||
</span>
|
||||
<h2
|
||||
style={{
|
||||
fontSize: 'clamp(2rem, 3.8vw, 2.8rem)',
|
||||
fontWeight: 800,
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
Design Your <span className="gold-gradient-text">Dream Setup</span>
|
||||
</h2>
|
||||
<p
|
||||
style={{
|
||||
fontSize: '1.05rem',
|
||||
color: '#94A3B8',
|
||||
maxWidth: '700px',
|
||||
margin: '0 auto',
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
Select your celebration type, color palette, and preferred backdrop elements below to generate your personalized setup preview in real time!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 3-Step Interactive Grid */}
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
||||
gap: '24px',
|
||||
marginBottom: '40px',
|
||||
}}
|
||||
>
|
||||
{/* STEP 1: Celebration Type */}
|
||||
<div
|
||||
className="glass-panel"
|
||||
style={{
|
||||
padding: '24px 20px',
|
||||
border: '1px solid rgba(212, 175, 55, 0.3)',
|
||||
borderRadius: '20px',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '16px' }}>
|
||||
<span
|
||||
style={{
|
||||
background: 'var(--color-gold-gradient)',
|
||||
color: '#000',
|
||||
width: '26px',
|
||||
height: '26px',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 800,
|
||||
fontSize: '0.85rem',
|
||||
}}
|
||||
>
|
||||
1
|
||||
</span>
|
||||
<h3 style={{ fontSize: '1.15rem', fontWeight: 800, color: '#FFFFFF', margin: 0 }}>
|
||||
Celebration Type
|
||||
</h3>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{eventTypes.map((et) => {
|
||||
const isSelected = selectedEventType === et.id;
|
||||
return (
|
||||
<button
|
||||
key={et.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedEventType(et.id)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
padding: '12px 14px',
|
||||
borderRadius: '12px',
|
||||
background: isSelected ? 'rgba(212, 175, 55, 0.15)' : 'rgba(255, 255, 255, 0.03)',
|
||||
border: isSelected ? '1.5px solid var(--color-gold)' : '1px solid rgba(255, 255, 255, 0.08)',
|
||||
color: isSelected ? '#FFFFFF' : '#CBD5E1',
|
||||
fontSize: '0.92rem',
|
||||
fontWeight: isSelected ? 700 : 500,
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '1.25rem' }}>{et.icon}</span>
|
||||
<span>{et.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* STEP 2: Color Palette */}
|
||||
<div
|
||||
className="glass-panel"
|
||||
style={{
|
||||
padding: '24px 20px',
|
||||
border: '1px solid rgba(224, 0, 111, 0.3)',
|
||||
borderRadius: '20px',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '16px' }}>
|
||||
<span
|
||||
style={{
|
||||
background: 'var(--color-pink-gradient)',
|
||||
color: '#FFF',
|
||||
width: '26px',
|
||||
height: '26px',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 800,
|
||||
fontSize: '0.85rem',
|
||||
}}
|
||||
>
|
||||
2
|
||||
</span>
|
||||
<h3 style={{ fontSize: '1.15rem', fontWeight: 800, color: '#FFFFFF', margin: 0 }}>
|
||||
Signature Palette
|
||||
</h3>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{palettes.map((p) => {
|
||||
const isSelected = selectedPalette === p.id;
|
||||
return (
|
||||
<button
|
||||
key={p.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedPalette(p.id)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
padding: '12px 14px',
|
||||
borderRadius: '12px',
|
||||
background: isSelected ? 'rgba(224, 0, 111, 0.15)' : 'rgba(255, 255, 255, 0.03)',
|
||||
border: isSelected ? '1.5px solid var(--color-pink-light)' : '1px solid rgba(255, 255, 255, 0.08)',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
||||
<span style={{ fontSize: '0.92rem', fontWeight: isSelected ? 700 : 600, color: isSelected ? '#FFFFFF' : '#CBD5E1' }}>
|
||||
{p.name}
|
||||
</span>
|
||||
<div style={{ display: 'flex', gap: '4px' }}>
|
||||
{p.colors.map((c, i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: c,
|
||||
border: '1px solid rgba(255,255,255,0.4)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<span style={{ fontSize: '0.78rem', color: '#94A3B8' }}>{p.desc}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* STEP 3: Decor Elements */}
|
||||
<div
|
||||
className="glass-panel"
|
||||
style={{
|
||||
padding: '24px 20px',
|
||||
border: '1px solid rgba(212, 175, 55, 0.3)',
|
||||
borderRadius: '20px',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '16px' }}>
|
||||
<span
|
||||
style={{
|
||||
background: 'var(--color-gold-gradient)',
|
||||
color: '#000',
|
||||
width: '26px',
|
||||
height: '26px',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 800,
|
||||
fontSize: '0.85rem',
|
||||
}}
|
||||
>
|
||||
3
|
||||
</span>
|
||||
<h3 style={{ fontSize: '1.15rem', fontWeight: 800, color: '#FFFFFF', margin: 0 }}>
|
||||
Backdrop & Elements
|
||||
</h3>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{decorElements.map((el) => {
|
||||
const isSelected = selectedElements.includes(el.id);
|
||||
return (
|
||||
<button
|
||||
key={el.id}
|
||||
type="button"
|
||||
onClick={() => toggleElement(el.id)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '10px 12px',
|
||||
borderRadius: '12px',
|
||||
background: isSelected ? 'rgba(212, 175, 55, 0.15)' : 'rgba(255, 255, 255, 0.03)',
|
||||
border: isSelected ? '1.5px solid var(--color-gold)' : '1px solid rgba(255, 255, 255, 0.08)',
|
||||
color: isSelected ? '#FFFFFF' : '#CBD5E1',
|
||||
fontSize: '0.88rem',
|
||||
fontWeight: isSelected ? 700 : 500,
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<span>{el.icon}</span>
|
||||
<span>{el.label}</span>
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '0.72rem',
|
||||
fontWeight: 700,
|
||||
color: isSelected ? 'var(--color-gold-light)' : '#64748B',
|
||||
background: isSelected ? 'rgba(212, 175, 55, 0.2)' : 'transparent',
|
||||
padding: '2px 6px',
|
||||
borderRadius: '6px',
|
||||
}}
|
||||
>
|
||||
{isSelected ? '✓ Added' : '+ Add'}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Customized Preview Banner & CTA */}
|
||||
<div
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(20, 20, 20, 0.95) 0%, rgba(10, 10, 10, 0.98) 100%)',
|
||||
border: '2px solid var(--color-gold)',
|
||||
borderRadius: '24px',
|
||||
padding: '24px 28px',
|
||||
boxShadow: '0 20px 50px rgba(0,0,0,0.8), 0 0 30px rgba(212, 175, 55, 0.2)',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: '1 1 400px' }}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '0.8rem',
|
||||
fontWeight: 800,
|
||||
color: 'var(--color-pink-light)',
|
||||
letterSpacing: '0.12em',
|
||||
textTransform: 'uppercase',
|
||||
display: 'block',
|
||||
marginBottom: '6px',
|
||||
}}
|
||||
>
|
||||
✦ YOUR CUSTOM EVENT SPECIFICATION ✦
|
||||
</span>
|
||||
<div style={{ fontSize: '1.25rem', fontWeight: 800, color: '#FFFFFF', marginBottom: '8px' }}>
|
||||
{currentEventType} <span style={{ color: 'var(--color-gold)' }}>•</span> {currentPalette?.name}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', marginTop: '4px' }}>
|
||||
{selectedElementLabels.map((lbl, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
style={{
|
||||
fontSize: '0.78rem',
|
||||
background: 'rgba(255, 255, 255, 0.08)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.15)',
|
||||
padding: '3px 8px',
|
||||
borderRadius: '8px',
|
||||
color: '#E2E8F0',
|
||||
}}
|
||||
>
|
||||
{lbl}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleApplyToBooking}
|
||||
className="btn-gold"
|
||||
style={{
|
||||
padding: '16px 32px',
|
||||
fontSize: '1rem',
|
||||
fontWeight: 800,
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 6px 25px rgba(212, 175, 55, 0.5)',
|
||||
}}
|
||||
>
|
||||
<span>✨</span> Lock In Design & Get Quote →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user