export function birthdayPartyCalculator(form: FormTs) {
// Pricing data
const venueBasePrices: Record<string, number> = {
'home': 0,
'park': 50,
'community-center': 150,
'restaurant': 200,
'party-venue': 350,
'trampoline-park': 400,
'bowling': 300,
'arcade': 350,
'escape-room': 400,
'pool': 300
};
const venuePerGuestPrices: Record<string, number> = {
'home': 0,
'park': 0,
'community-center': 0,
'restaurant': 15,
'party-venue': 20,
'trampoline-park': 25,
'bowling': 18,
'arcade': 22,
'escape-room': 30,
'pool': 15
};
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Birthday Party Cost Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Party Basics Section
const basicsSection = form.addSubform('basics', { title: '🎂 Party Basics' });
basicsSection.addRow(row => {
row.addDropdown('ageGroup', {
label: 'Birthday Child Age',
options: [
{ id: 'toddler', name: 'Toddler (1-3 years)' },
{ id: 'preschool', name: 'Preschool (4-5 years)' },
{ id: 'kids', name: 'Kids (6-9 years)' },
{ id: 'tweens', name: 'Tweens (10-12 years)' },
{ id: 'teens', name: 'Teens (13-17 years)' },
{ id: 'adult', name: 'Adult (18+)' }
],
defaultValue: 'kids',
isRequired: true
}, '1fr');
row.addInteger('guestCount', {
label: 'Number of Guests',
min: 1,
max: 200,
defaultValue: 15,
isRequired: true,
tooltip: 'Including the birthday child'
}, '1fr');
});
basicsSection.addRow(row => {
row.addDropdown('partyDuration', {
label: 'Party Duration',
options: [
{ id: '1', name: '1 hour' },
{ id: '2', name: '2 hours' },
{ id: '3', name: '3 hours' },
{ id: '4', name: '4 hours' },
{ id: '5', name: 'Half day (5+ hours)' }
],
defaultValue: '2',
isRequired: true
}, '1fr');
row.addDropdown('venue', {
label: 'Venue',
options: [
{ id: 'home', name: 'At Home' },
{ id: 'park', name: 'Park/Outdoor' },
{ id: 'community-center', name: 'Community Center' },
{ id: 'restaurant', name: 'Restaurant/Pizza Place' },
{ id: 'party-venue', name: 'Party Venue/Play Space' },
{ id: 'trampoline-park', name: 'Trampoline Park' },
{ id: 'bowling', name: 'Bowling Alley' },
{ id: 'arcade', name: 'Arcade/Game Center' },
{ id: 'escape-room', name: 'Escape Room' },
{ id: 'pool', name: 'Pool/Water Park' }
],
defaultValue: 'home',
isRequired: true
}, '1fr');
});
// Food & Drinks Section
const foodSection = form.addSubform('food', { title: '🍕 Food & Drinks' });
foodSection.addRow(row => {
row.addDropdown('cakeType', {
label: 'Birthday Cake',
options: [
{ id: 'none', name: 'No Cake' },
{ id: 'homemade', name: 'Homemade ($30)' },
{ id: 'grocery', name: 'Grocery Store Cake ($40)' },
{ id: 'bakery-basic', name: 'Bakery - Basic ($60)' },
{ id: 'bakery-custom', name: 'Bakery - Custom Design ($120)' },
{ id: 'specialty', name: 'Specialty/Themed Cake ($200+)' }
],
defaultValue: 'bakery-basic',
isRequired: true
}, '1fr');
row.addDropdown('foodStyle', {
label: 'Food Style',
options: [
{ id: 'none', name: 'No Food (Cake Only)' },
{ id: 'snacks', name: 'Snacks & Finger Foods ($5/guest)' },
{ id: 'pizza', name: 'Pizza & Drinks ($8/guest)' },
{ id: 'catered-basic', name: 'Basic Catering ($15/guest)' },
{ id: 'catered-full', name: 'Full Catering ($25/guest)' },
{ id: 'bbq', name: 'BBQ/Cookout ($12/guest)' }
],
defaultValue: 'pizza'
}, '1fr');
});
foodSection.addRow(row => {
row.addCheckbox('iceCream', {
label: 'Ice Cream/Sundae Bar',
defaultValue: true,
tooltip: '+$3 per guest'
}, '1fr');
row.addCheckbox('candyBar', {
label: 'Candy/Dessert Bar',
defaultValue: false,
tooltip: '+$4 per guest'
}, '1fr');
});
// Decorations Section
const decorSection = form.addSubform('decorations', { title: '🎈 Decorations & Theme' });
decorSection.addRow(row => {
row.addDropdown('decorLevel', {
label: 'Decoration Level',
options: [
{ id: 'minimal', name: 'Minimal - Basic balloons & banner ($25)' },
{ id: 'standard', name: 'Standard - Theme decorations ($75)' },
{ id: 'elaborate', name: 'Elaborate - Full setup ($150)' },
{ id: 'professional', name: 'Professional Decorator ($300+)' }
],
defaultValue: 'standard'
}, '1fr');
row.addDropdown('theme', {
label: 'Party Theme',
options: [
{ id: 'generic', name: 'Generic Birthday' },
{ id: 'licensed', name: 'Licensed Character (+$30)' },
{ id: 'custom', name: 'Custom Theme (+$50)' }
],
defaultValue: 'generic'
}, '1fr');
});
decorSection.addRow(row => {
row.addCheckbox('balloonArch', {
label: 'Balloon Arch/Garland',
defaultValue: false,
tooltip: '+$80 for professional balloon arrangement'
}, '1fr');
row.addCheckbox('photoBooth', {
label: 'Photo Backdrop/Props',
defaultValue: false,
tooltip: '+$50 for photo booth setup'
}, '1fr');
});
// Entertainment Section
const entertainmentSection = form.addSubform('entertainment', { title: '🎪 Entertainment' });
entertainmentSection.addRow(row => {
row.addDropdown('entertainment', {
label: 'Main Entertainment',
options: [
{ id: 'none', name: 'No Entertainment/Self-Directed' },
{ id: 'diy-games', name: 'DIY Games & Activities ($30)' },
{ id: 'magician', name: 'Magician ($200)' },
{ id: 'clown', name: 'Clown/Face Painter ($150)' },
{ id: 'character', name: 'Character Appearance ($250)' },
{ id: 'dj', name: 'DJ/Music ($300)' },
{ id: 'bounce-house', name: 'Bounce House Rental ($200)' },
{ id: 'petting-zoo', name: 'Petting Zoo ($350)' },
{ id: 'science-show', name: 'Science Show ($275)' },
{ id: 'crafts', name: 'Craft Party ($15/kid)' }
],
defaultValue: 'diy-games'
}, '1fr');
row.addDropdown('activities', {
label: 'Additional Activities',
options: [
{ id: 'none', name: 'None' },
{ id: 'pinata', name: 'Pinata ($35)' },
{ id: 'games', name: 'Party Games Kit ($50)' },
{ id: 'crafts-kit', name: 'Craft Kits ($8/kid)' },
{ id: 'treasure-hunt', name: 'Treasure Hunt Setup ($40)' }
],
defaultValue: 'pinata'
}, '1fr');
});
// Party Favors Section
const favorsSection = form.addSubform('favors', { title: '🎁 Party Favors & Extras' });
favorsSection.addRow(row => {
row.addDropdown('favorBags', {
label: 'Favor Bags',
options: [
{ id: 'none', name: 'No Favor Bags' },
{ id: 'basic', name: 'Basic - Candy & small toys ($5/bag)' },
{ id: 'themed', name: 'Themed Favors ($10/bag)' },
{ id: 'premium', name: 'Premium Gift Bags ($15/bag)' }
],
defaultValue: 'basic'
}, '1fr');
row.addDropdown('invitations', {
label: 'Invitations',
options: [
{ id: 'digital', name: 'Digital/E-vites (Free)' },
{ id: 'printable', name: 'Printable ($10)' },
{ id: 'store', name: 'Store-bought ($15)' },
{ id: 'custom', name: 'Custom Printed ($40)' }
],
defaultValue: 'digital'
}, '1fr');
});
favorsSection.addRow(row => {
row.addCheckbox('thankYouCards', {
label: 'Thank You Cards',
defaultValue: true,
tooltip: '+$15 for matching thank you cards'
}, '1fr');
row.addCheckbox('partyHats', {
label: 'Party Hats & Accessories',
defaultValue: true,
tooltip: '+$20 for hats, noisemakers, etc.'
}, '1fr');
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Cost Breakdown Section
const breakdownSection = form.addSubform('breakdown', { title: '📊 Cost Breakdown', isCollapsible: false });
breakdownSection.addRow(row => {
row.addPriceDisplay('venueCost', {
label: 'Venue',
computedValue: () => {
const venue = basicsSection.dropdown('venue')?.value() || 'home';
const guests = basicsSection.integer('guestCount')?.value() || 15;
const duration = parseInt(basicsSection.dropdown('partyDuration')?.value() || '2');
let base = venueBasePrices[venue] || 0;
const perGuest = venuePerGuestPrices[venue] || 0;
if (venue !== 'home' && venue !== 'park' && duration > 2) {
base += (duration - 2) * 50;
}
return base + (perGuest * guests);
},
variant: 'default'
}, '1fr');
row.addPriceDisplay('foodCost', {
label: 'Food & Cake',
computedValue: () => {
const guests = basicsSection.integer('guestCount')?.value() || 15;
const cake = foodSection.dropdown('cakeType')?.value() || 'bakery-basic';
const food = foodSection.dropdown('foodStyle')?.value() || 'pizza';
const cakePrices: Record<string, number> = {
'none': 0, 'homemade': 30, 'grocery': 40, 'bakery-basic': 60,
'bakery-custom': 120, 'specialty': 200
};
const foodPerGuest: Record<string, number> = {
'none': 0, 'snacks': 5, 'pizza': 8, 'catered-basic': 15,
'catered-full': 25, 'bbq': 12
};
let cost = cakePrices[cake] || 60;
cost += (foodPerGuest[food] || 8) * guests;
if (foodSection.checkbox('iceCream')?.value()) cost += guests * 3;
if (foodSection.checkbox('candyBar')?.value()) cost += guests * 4;
return cost;
},
variant: 'default'
}, '1fr');
});
breakdownSection.addRow(row => {
row.addPriceDisplay('decorCost', {
label: 'Decorations',
computedValue: () => {
const level = decorSection.dropdown('decorLevel')?.value() || 'standard';
const theme = decorSection.dropdown('theme')?.value() || 'generic';
const levelPrices: Record<string, number> = {
'minimal': 25, 'standard': 75, 'elaborate': 150, 'professional': 300
};
let cost = levelPrices[level] || 75;
if (theme === 'licensed') cost += 30;
if (theme === 'custom') cost += 50;
if (decorSection.checkbox('balloonArch')?.value()) cost += 80;
if (decorSection.checkbox('photoBooth')?.value()) cost += 50;
return cost;
},
variant: 'default'
}, '1fr');
row.addPriceDisplay('entertainmentCost', {
label: 'Entertainment',
computedValue: () => {
const guests = basicsSection.integer('guestCount')?.value() || 15;
const entertainment = entertainmentSection.dropdown('entertainment')?.value() || 'diy-games';
const activities = entertainmentSection.dropdown('activities')?.value() || 'pinata';
const entertainmentPrices: Record<string, number> = {
'none': 0, 'diy-games': 30, 'magician': 200, 'clown': 150,
'character': 250, 'dj': 300, 'bounce-house': 200,
'petting-zoo': 350, 'science-show': 275
};
const activityPrices: Record<string, number> = {
'none': 0, 'pinata': 35, 'games': 50, 'treasure-hunt': 40
};
let cost = entertainmentPrices[entertainment] || 0;
if (entertainment === 'crafts') cost = guests * 15;
cost += activityPrices[activities] || 0;
if (activities === 'crafts-kit') cost = guests * 8;
return cost;
},
variant: 'default'
}, '1fr');
});
breakdownSection.addRow(row => {
row.addPriceDisplay('favorsCost', {
label: 'Favors & Extras',
computedValue: () => {
const guests = basicsSection.integer('guestCount')?.value() || 15;
const favors = favorsSection.dropdown('favorBags')?.value() || 'basic';
const invites = favorsSection.dropdown('invitations')?.value() || 'digital';
const favorPrices: Record<string, number> = {
'none': 0, 'basic': 5, 'themed': 10, 'premium': 15
};
const invitePrices: Record<string, number> = {
'digital': 0, 'printable': 10, 'store': 15, 'custom': 40
};
let cost = (favorPrices[favors] || 5) * guests;
cost += invitePrices[invites] || 0;
if (favorsSection.checkbox('thankYouCards')?.value()) cost += 15;
if (favorsSection.checkbox('partyHats')?.value()) cost += 20;
return cost;
},
variant: 'default'
}, '1fr');
row.addPriceDisplay('perGuestCost', {
label: 'Cost Per Guest',
computedValue: () => {
const guests = basicsSection.integer('guestCount')?.value() || 15;
const venue = basicsSection.dropdown('venue')?.value() || 'home';
const duration = parseInt(basicsSection.dropdown('partyDuration')?.value() || '2');
const cake = foodSection.dropdown('cakeType')?.value() || 'bakery-basic';
const food = foodSection.dropdown('foodStyle')?.value() || 'pizza';
const level = decorSection.dropdown('decorLevel')?.value() || 'standard';
const theme = decorSection.dropdown('theme')?.value() || 'generic';
const entertainment = entertainmentSection.dropdown('entertainment')?.value() || 'diy-games';
const activities = entertainmentSection.dropdown('activities')?.value() || 'pinata';
const favors = favorsSection.dropdown('favorBags')?.value() || 'basic';
const invites = favorsSection.dropdown('invitations')?.value() || 'digital';
let total = 0;
// Venue
let base = venueBasePrices[venue] || 0;
const perGuest = venuePerGuestPrices[venue] || 0;
if (venue !== 'home' && venue !== 'park' && duration > 2) {
base += (duration - 2) * 50;
}
total += base + (perGuest * guests);
// Food
const cakePrices: Record<string, number> = {
'none': 0, 'homemade': 30, 'grocery': 40, 'bakery-basic': 60,
'bakery-custom': 120, 'specialty': 200
};
const foodPerGuest: Record<string, number> = {
'none': 0, 'snacks': 5, 'pizza': 8, 'catered-basic': 15,
'catered-full': 25, 'bbq': 12
};
total += cakePrices[cake] || 60;
total += (foodPerGuest[food] || 8) * guests;
if (foodSection.checkbox('iceCream')?.value()) total += guests * 3;
if (foodSection.checkbox('candyBar')?.value()) total += guests * 4;
// Decor
const levelPrices: Record<string, number> = {
'minimal': 25, 'standard': 75, 'elaborate': 150, 'professional': 300
};
total += levelPrices[level] || 75;
if (theme === 'licensed') total += 30;
if (theme === 'custom') total += 50;
if (decorSection.checkbox('balloonArch')?.value()) total += 80;
if (decorSection.checkbox('photoBooth')?.value()) total += 50;
// Entertainment
const entertainmentPrices: Record<string, number> = {
'none': 0, 'diy-games': 30, 'magician': 200, 'clown': 150,
'character': 250, 'dj': 300, 'bounce-house': 200,
'petting-zoo': 350, 'science-show': 275
};
const activityPrices: Record<string, number> = {
'none': 0, 'pinata': 35, 'games': 50, 'treasure-hunt': 40
};
let entCost = entertainmentPrices[entertainment] || 0;
if (entertainment === 'crafts') entCost = guests * 15;
total += entCost;
let actCost = activityPrices[activities] || 0;
if (activities === 'crafts-kit') actCost = guests * 8;
total += actCost;
// Favors
const favorPrices: Record<string, number> = {
'none': 0, 'basic': 5, 'themed': 10, 'premium': 15
};
const invitePrices: Record<string, number> = {
'digital': 0, 'printable': 10, 'store': 15, 'custom': 40
};
total += (favorPrices[favors] || 5) * guests;
total += invitePrices[invites] || 0;
if (favorsSection.checkbox('thankYouCards')?.value()) total += 15;
if (favorsSection.checkbox('partyHats')?.value()) total += 20;
return Math.round(total / guests);
},
variant: 'default'
}, '1fr');
});
// Summary Section
const summarySection = form.addSubform('summary', {
title: '🎉 Total Party Cost',
isCollapsible: false,
sticky: 'bottom'
});
summarySection.addRow(row => {
row.addPriceDisplay('totalCost', {
label: 'Estimated Total',
computedValue: () => {
const guests = basicsSection.integer('guestCount')?.value() || 15;
const venue = basicsSection.dropdown('venue')?.value() || 'home';
const duration = parseInt(basicsSection.dropdown('partyDuration')?.value() || '2');
const cake = foodSection.dropdown('cakeType')?.value() || 'bakery-basic';
const food = foodSection.dropdown('foodStyle')?.value() || 'pizza';
const level = decorSection.dropdown('decorLevel')?.value() || 'standard';
const theme = decorSection.dropdown('theme')?.value() || 'generic';
const entertainment = entertainmentSection.dropdown('entertainment')?.value() || 'diy-games';
const activities = entertainmentSection.dropdown('activities')?.value() || 'pinata';
const favors = favorsSection.dropdown('favorBags')?.value() || 'basic';
const invites = favorsSection.dropdown('invitations')?.value() || 'digital';
let total = 0;
// Venue
let base = venueBasePrices[venue] || 0;
const perGuest = venuePerGuestPrices[venue] || 0;
if (venue !== 'home' && venue !== 'park' && duration > 2) {
base += (duration - 2) * 50;
}
total += base + (perGuest * guests);
// Food
const cakePrices: Record<string, number> = {
'none': 0, 'homemade': 30, 'grocery': 40, 'bakery-basic': 60,
'bakery-custom': 120, 'specialty': 200
};
const foodPerGuest: Record<string, number> = {
'none': 0, 'snacks': 5, 'pizza': 8, 'catered-basic': 15,
'catered-full': 25, 'bbq': 12
};
total += cakePrices[cake] || 60;
total += (foodPerGuest[food] || 8) * guests;
if (foodSection.checkbox('iceCream')?.value()) total += guests * 3;
if (foodSection.checkbox('candyBar')?.value()) total += guests * 4;
// Decor
const levelPrices: Record<string, number> = {
'minimal': 25, 'standard': 75, 'elaborate': 150, 'professional': 300
};
total += levelPrices[level] || 75;
if (theme === 'licensed') total += 30;
if (theme === 'custom') total += 50;
if (decorSection.checkbox('balloonArch')?.value()) total += 80;
if (decorSection.checkbox('photoBooth')?.value()) total += 50;
// Entertainment
const entertainmentPrices: Record<string, number> = {
'none': 0, 'diy-games': 30, 'magician': 200, 'clown': 150,
'character': 250, 'dj': 300, 'bounce-house': 200,
'petting-zoo': 350, 'science-show': 275
};
const activityPrices: Record<string, number> = {
'none': 0, 'pinata': 35, 'games': 50, 'treasure-hunt': 40
};
let entCost = entertainmentPrices[entertainment] || 0;
if (entertainment === 'crafts') entCost = guests * 15;
total += entCost;
let actCost = activityPrices[activities] || 0;
if (activities === 'crafts-kit') actCost = guests * 8;
total += actCost;
// Favors
const favorPrices: Record<string, number> = {
'none': 0, 'basic': 5, 'themed': 10, 'premium': 15
};
const invitePrices: Record<string, number> = {
'digital': 0, 'printable': 10, 'store': 15, 'custom': 40
};
total += (favorPrices[favors] || 5) * guests;
total += invitePrices[invites] || 0;
if (favorsSection.checkbox('thankYouCards')?.value()) total += 15;
if (favorsSection.checkbox('partyHats')?.value()) total += 20;
return total;
},
variant: 'large'
});
});
summarySection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'Prices are estimates based on average costs. Actual prices may vary by location and vendor.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Get Party Quote'
});
}