export function bathroomRenovationCalculator(form: FormTs) {
// Renovation level base costs
const levelBaseCosts: Record<string, number> = {
'cosmetic': 3000, // Paint, fixtures, accessories
'partial': 8000, // Some new items, refresh
'full': 18000, // Complete gut and redo
'luxury': 35000 // High-end, custom
};
// Bathroom size multipliers
const sizeMultipliers: Record<string, number> = {
'half': 0.6, // Half bath/powder room
'small': 0.85, // Small full bath (40-60 sq ft)
'standard': 1.0, // Standard bath (60-100 sq ft)
'large': 1.35, // Large master bath (100-150 sq ft)
'luxury': 1.8 // Spa/luxury bath (150+ sq ft)
};
// Fixture quality multipliers
const qualityMultipliers: Record<string, number> = {
'builder': 0.8,
'standard': 1.0,
'upgraded': 1.4,
'premium': 2.0,
'luxury': 3.0
};
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Bathroom Renovation Estimate',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Service Location Section
const locationSection = form.addSubform('serviceLocation', { title: '📍 Project Location' });
locationSection.addRow(row => {
row.addAddress('propertyAddress', {
label: 'Property Address',
placeholder: 'Enter your property address...',
showMap: true,
showDistance: true,
referenceAddress: {
formattedAddress: 'Service Center, Denver, CO',
coordinates: { lat: 39.7392, lng: -104.9903 }
},
restrictToCountries: ['US', 'CA'],
distanceUnit: 'miles',
isRequired: true
});
});
locationSection.addRow(row => {
row.addTextPanel('serviceAreaInfo', {
computedValue: () => {
const addressField = locationSection.address('propertyAddress');
const miles = addressField?.distance();
if (miles == null) return '📍 Enter address to check service area';
if (miles <= 25) return '📍 Within service area - No travel fee';
if (miles <= 50) return '📍 Extended area - $150 travel fee';
if (miles <= 75) return '📍 Remote area - $300 travel fee';
return '📍 Outside service area - Please call for availability';
},
customStyles: { 'font-size': '0.9rem', 'color': '#0369a1', 'background': '#e0f2fe', 'padding': '10px', 'border-radius': '6px' }
});
});
// Project Scope Section
const scopeSection = form.addSubform('projectScope', { title: '📐 Project Scope' });
scopeSection.addRow(row => {
row.addRadioButton('renovationLevel', {
label: 'Renovation Level',
options: [
{ id: 'cosmetic', name: 'Cosmetic Update ($3,000+) - Paint, fixtures, accessories' },
{ id: 'partial', name: 'Partial Remodel ($8,000+) - New vanity, toilet, fixtures' },
{ id: 'full', name: 'Full Remodel ($18,000+) - Complete gut, new everything' },
{ id: 'luxury', name: 'Luxury Renovation ($35,000+) - High-end, custom design' }
],
defaultValue: 'full',
orientation: 'vertical',
isRequired: true
});
});
scopeSection.addRow(row => {
row.addDropdown('bathroomSize', {
label: 'Bathroom Size',
options: [
{ id: 'half', name: 'Half Bath/Powder Room (20-40 sq ft)' },
{ id: 'small', name: 'Small Full Bath (40-60 sq ft)' },
{ id: 'standard', name: 'Standard Bathroom (60-100 sq ft)' },
{ id: 'large', name: 'Large Master Bath (100-150 sq ft)' },
{ id: 'luxury', name: 'Spa/Luxury Bath (150+ sq ft)' }
],
defaultValue: 'standard',
isRequired: true
}, '1fr');
row.addDropdown('bathroomType', {
label: 'Bathroom Type',
options: [
{ id: 'powder', name: 'Powder Room' },
{ id: 'guest', name: 'Guest Bathroom' },
{ id: 'hall', name: 'Hall Bathroom' },
{ id: 'ensuite', name: 'En-suite/Master' },
{ id: 'jack-jill', name: 'Jack & Jill' }
],
defaultValue: 'ensuite'
}, '1fr');
});
// Fixture Quality Section
const qualitySection = form.addSubform('fixtureQuality', { title: '⭐ Fixture Quality' });
qualitySection.addRow(row => {
row.addRadioButton('overallQuality', {
label: 'Overall Fixture Quality',
options: [
{ id: 'builder', name: 'Builder Grade (-20%) - Basic, functional' },
{ id: 'standard', name: 'Standard - Good quality, common brands' },
{ id: 'upgraded', name: 'Upgraded (+40%) - Better brands, more features' },
{ id: 'premium', name: 'Premium (+100%) - High-end brands' },
{ id: 'luxury', name: 'Luxury (+200%) - Designer, custom' }
],
defaultValue: 'standard',
orientation: 'vertical'
});
});
// Major Components Section
const componentsSection = form.addSubform('majorComponents', {
title: '🚿 Major Components',
isVisible: () => {
const level = scopeSection.radioButton('renovationLevel')?.value();
return level !== 'cosmetic';
}
});
componentsSection.addRow(row => {
row.addCheckbox('newVanity', {
label: 'New Vanity & Countertop',
defaultValue: true
}, '1fr');
row.addDropdown('vanityType', {
label: 'Vanity Style',
options: [
{ id: 'single', name: 'Single Vanity (+$500-2,000)' },
{ id: 'double', name: 'Double Vanity (+$1,000-4,000)' },
{ id: 'floating', name: 'Floating Vanity (+$800-3,000)' },
{ id: 'custom', name: 'Custom Built (+$2,000-8,000)' }
],
defaultValue: 'single',
isVisible: () => componentsSection.checkbox('newVanity')?.value() === true
}, '1fr');
});
componentsSection.addRow(row => {
row.addCheckbox('newToilet', {
label: 'New Toilet (+$200-1,500)',
defaultValue: true
}, '1fr');
row.addCheckbox('newShower', {
label: 'New Shower/Tub',
defaultValue: true
}, '1fr');
});
componentsSection.addRow(row => {
row.addDropdown('showerType', {
label: 'Shower/Tub Configuration',
options: [
{ id: 'tub-shower', name: 'Tub/Shower Combo (+$1,500-5,000)' },
{ id: 'shower-only', name: 'Shower Stall (+$2,000-8,000)' },
{ id: 'walk-in', name: 'Walk-in Shower (+$4,000-15,000)' },
{ id: 'tub-separate', name: 'Separate Tub & Shower (+$5,000-20,000)' },
{ id: 'freestanding', name: 'Freestanding Tub (+$2,000-10,000)' }
],
defaultValue: 'tub-shower',
isVisible: () => componentsSection.checkbox('newShower')?.value() === true
}, '1fr');
});
// Surfaces Section
const surfacesSection = form.addSubform('surfaces', {
title: '🎨 Surfaces & Finishes',
isVisible: () => {
const level = scopeSection.radioButton('renovationLevel')?.value();
return level === 'full' || level === 'luxury';
}
});
surfacesSection.addRow(row => {
row.addDropdown('floorTile', {
label: 'Floor Tile',
options: [
{ id: 'ceramic', name: 'Ceramic ($5-15/sq ft)' },
{ id: 'porcelain', name: 'Porcelain ($8-25/sq ft)' },
{ id: 'natural-stone', name: 'Natural Stone ($15-50/sq ft)' },
{ id: 'vinyl', name: 'Luxury Vinyl ($4-10/sq ft)' },
{ id: 'heated', name: 'Heated Tile (+$10-20/sq ft)' }
],
defaultValue: 'porcelain'
}, '1fr');
row.addDropdown('wallTile', {
label: 'Wall/Shower Tile',
options: [
{ id: 'basic', name: 'Basic Ceramic ($8-15/sq ft)' },
{ id: 'subway', name: 'Subway Tile ($10-20/sq ft)' },
{ id: 'decorative', name: 'Decorative/Mosaic ($20-50/sq ft)' },
{ id: 'natural-stone', name: 'Natural Stone ($25-75/sq ft)' },
{ id: 'glass', name: 'Glass Tile ($30-80/sq ft)' }
],
defaultValue: 'subway'
}, '1fr');
});
surfacesSection.addRow(row => {
row.addDropdown('countertop', {
label: 'Countertop Material',
options: [
{ id: 'laminate', name: 'Laminate ($20-50/sq ft)' },
{ id: 'solid-surface', name: 'Solid Surface ($50-100/sq ft)' },
{ id: 'quartz', name: 'Quartz ($75-150/sq ft)' },
{ id: 'granite', name: 'Granite ($80-150/sq ft)' },
{ id: 'marble', name: 'Marble ($100-250/sq ft)' }
],
defaultValue: 'quartz'
}, '1fr');
});
// Additional Features Section
const featuresSection = form.addSubform('additionalFeatures', { title: '✨ Additional Features' });
featuresSection.addRow(row => {
row.addCheckbox('newLighting', {
label: 'New Lighting Fixtures (+$200-1,500)',
defaultValue: true
}, '1fr');
row.addCheckbox('exhausFan', {
label: 'New Exhaust Fan (+$150-500)',
defaultValue: true
}, '1fr');
});
featuresSection.addRow(row => {
row.addCheckbox('mirror', {
label: 'New Mirror (+$100-1,000)',
defaultValue: true
}, '1fr');
row.addCheckbox('medicine', {
label: 'Medicine Cabinet (+$150-800)',
defaultValue: false
}, '1fr');
});
featuresSection.addRow(row => {
row.addCheckbox('accessories', {
label: 'Towel Bars, TP Holder, etc. (+$100-500)',
defaultValue: true
}, '1fr');
row.addCheckbox('storage', {
label: 'Built-in Storage/Niches (+$300-1,500)',
defaultValue: false
}, '1fr');
});
// Plumbing & Electrical Section
const plumbingSection = form.addSubform('plumbing', { title: '🔧 Plumbing & Electrical' });
plumbingSection.addRow(row => {
row.addRadioButton('plumbingWork', {
label: 'Plumbing Modifications',
options: [
{ id: 'none', name: 'No changes to layout' },
{ id: 'minor', name: 'Minor updates (+$500-1,500)' },
{ id: 'moderate', name: 'Some fixture relocation (+$2,000-5,000)' },
{ id: 'major', name: 'Major layout changes (+$5,000-15,000)' }
],
defaultValue: 'minor',
orientation: 'vertical'
});
});
plumbingSection.addRow(row => {
row.addRadioButton('electricalWork', {
label: 'Electrical Work',
options: [
{ id: 'none', name: 'No changes needed' },
{ id: 'minor', name: 'Add outlets/update fixtures (+$300-800)' },
{ id: 'moderate', name: 'New circuits, GFCIs (+$800-2,000)' },
{ id: 'major', name: 'Panel upgrade, rewiring (+$2,000-5,000)' }
],
defaultValue: 'minor',
orientation: 'vertical'
});
});
// Special Features Section
const specialSection = form.addSubform('specialFeatures', { title: '💎 Special Features' });
specialSection.addRow(row => {
row.addCheckbox('heatedFloor', {
label: 'Heated Floor (+$800-2,500)',
defaultValue: false
}, '1fr');
row.addCheckbox('steamShower', {
label: 'Steam Shower (+$2,500-6,000)',
defaultValue: false
}, '1fr');
});
specialSection.addRow(row => {
row.addCheckbox('bidet', {
label: 'Bidet/Bidet Seat (+$300-2,000)',
defaultValue: false
}, '1fr');
row.addCheckbox('smartMirror', {
label: 'Smart Mirror (+$500-2,500)',
defaultValue: false
}, '1fr');
});
specialSection.addRow(row => {
row.addCheckbox('glassEnclosure', {
label: 'Frameless Glass Enclosure (+$1,500-5,000)',
defaultValue: false
}, '1fr');
row.addCheckbox('customTile', {
label: 'Custom Tile Work/Patterns (+$1,000-5,000)',
defaultValue: false
}, '1fr');
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Helper to calculate travel fee
const getTravelFee = () => {
const addressField = locationSection.address('propertyAddress');
const miles = addressField?.distance();
if (miles == null || miles <= 25) return 0;
if (miles <= 50) return 150;
if (miles <= 75) return 300;
return 450;
};
// Price Summary Section
const summarySection = form.addSubform('summary', { title: '💰 Your Estimate', isCollapsible: false });
const getBasePrice = () => {
const level = scopeSection.radioButton('renovationLevel')?.value() || 'full';
const size = scopeSection.dropdown('bathroomSize')?.value() || 'standard';
const quality = qualitySection.radioButton('overallQuality')?.value() || 'standard';
const base = levelBaseCosts[level] || 18000;
const sizeMult = sizeMultipliers[size] || 1;
const qualMult = qualityMultipliers[quality] || 1;
return base * sizeMult * qualMult;
};
summarySection.addRow(row => {
row.addPriceDisplay('basePrice', {
label: 'Base Renovation Cost',
computedValue: () => Math.round(getBasePrice()),
variant: 'default'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('fixturesPrice', {
label: 'Major Fixtures',
computedValue: () => {
let total = 0;
const quality = qualitySection.radioButton('overallQuality')?.value() || 'standard';
const qualMult = qualityMultipliers[quality] || 1;
if (componentsSection.checkbox('newVanity')?.value()) {
const vanityType = componentsSection.dropdown('vanityType')?.value() || 'single';
const vanityCosts: Record<string, number> = {
'single': 1200, 'double': 2400, 'floating': 1800, 'custom': 4500
};
total += (vanityCosts[vanityType] || 1200) * (qualMult * 0.5 + 0.5);
}
if (componentsSection.checkbox('newToilet')?.value()) {
total += 600 * (qualMult * 0.5 + 0.5);
}
if (componentsSection.checkbox('newShower')?.value()) {
const showerType = componentsSection.dropdown('showerType')?.value() || 'tub-shower';
const showerCosts: Record<string, number> = {
'tub-shower': 3000, 'shower-only': 4500, 'walk-in': 8500,
'tub-separate': 12000, 'freestanding': 5500
};
total += (showerCosts[showerType] || 3000) * (qualMult * 0.5 + 0.5);
}
return Math.round(total);
},
variant: 'default',
prefix: '+',
isVisible: () => {
const level = scopeSection.radioButton('renovationLevel')?.value();
return level !== 'cosmetic';
}
}, '1fr');
row.addPriceDisplay('plumbingPrice', {
label: 'Plumbing & Electrical',
computedValue: () => {
let total = 0;
const plumbing = plumbingSection.radioButton('plumbingWork')?.value() || 'minor';
const plumbingCosts: Record<string, number> = {
'none': 0, 'minor': 1000, 'moderate': 3500, 'major': 10000
};
total += plumbingCosts[plumbing] || 0;
const electrical = plumbingSection.radioButton('electricalWork')?.value() || 'minor';
const electricalCosts: Record<string, number> = {
'none': 0, 'minor': 550, 'moderate': 1400, 'major': 3500
};
total += electricalCosts[electrical] || 0;
return Math.round(total);
},
variant: 'default',
prefix: '+'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('featuresPrice', {
label: 'Fixtures & Features',
computedValue: () => {
let total = 0;
const quality = qualitySection.radioButton('overallQuality')?.value() || 'standard';
const qualMult = qualityMultipliers[quality] || 1;
const midMult = qualMult * 0.4 + 0.6;
if (featuresSection.checkbox('newLighting')?.value()) total += 600 * midMult;
if (featuresSection.checkbox('exhausFan')?.value()) total += 300 * midMult;
if (featuresSection.checkbox('mirror')?.value()) total += 400 * midMult;
if (featuresSection.checkbox('medicine')?.value()) total += 400 * midMult;
if (featuresSection.checkbox('accessories')?.value()) total += 250 * midMult;
if (featuresSection.checkbox('storage')?.value()) total += 800 * midMult;
return Math.round(total);
},
variant: 'default',
prefix: '+'
}, '1fr');
row.addPriceDisplay('specialPrice', {
label: 'Special Features',
computedValue: () => {
let total = 0;
if (specialSection.checkbox('heatedFloor')?.value()) total += 1500;
if (specialSection.checkbox('steamShower')?.value()) total += 4000;
if (specialSection.checkbox('bidet')?.value()) total += 1000;
if (specialSection.checkbox('smartMirror')?.value()) total += 1200;
if (specialSection.checkbox('glassEnclosure')?.value()) total += 3000;
if (specialSection.checkbox('customTile')?.value()) total += 2500;
return Math.round(total);
},
variant: 'default',
prefix: '+'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('travelFee', {
label: 'Travel Fee',
computedValue: () => getTravelFee(),
variant: 'default',
prefix: '+',
isVisible: () => getTravelFee() > 0
});
});
summarySection.addSpacer({showLine: true, lineStyle: 'solid', lineColor: '#e2e8f0' });
summarySection.addRow(row => {
row.addPriceDisplay('totalEstimate', {
label: 'Total Estimated Cost',
computedValue: () => {
let total = getBasePrice();
// Major fixtures
const quality = qualitySection.radioButton('overallQuality')?.value() || 'standard';
const qualMult = qualityMultipliers[quality] || 1;
if (componentsSection.checkbox('newVanity')?.value()) {
const vanityType = componentsSection.dropdown('vanityType')?.value() || 'single';
const vanityCosts: Record<string, number> = {
'single': 1200, 'double': 2400, 'floating': 1800, 'custom': 4500
};
total += (vanityCosts[vanityType] || 1200) * (qualMult * 0.5 + 0.5);
}
if (componentsSection.checkbox('newToilet')?.value()) {
total += 600 * (qualMult * 0.5 + 0.5);
}
if (componentsSection.checkbox('newShower')?.value()) {
const showerType = componentsSection.dropdown('showerType')?.value() || 'tub-shower';
const showerCosts: Record<string, number> = {
'tub-shower': 3000, 'shower-only': 4500, 'walk-in': 8500,
'tub-separate': 12000, 'freestanding': 5500
};
total += (showerCosts[showerType] || 3000) * (qualMult * 0.5 + 0.5);
}
// Plumbing & electrical
const plumbing = plumbingSection.radioButton('plumbingWork')?.value() || 'minor';
const plumbingCosts: Record<string, number> = {
'none': 0, 'minor': 1000, 'moderate': 3500, 'major': 10000
};
total += plumbingCosts[plumbing] || 0;
const electrical = plumbingSection.radioButton('electricalWork')?.value() || 'minor';
const electricalCosts: Record<string, number> = {
'none': 0, 'minor': 550, 'moderate': 1400, 'major': 3500
};
total += electricalCosts[electrical] || 0;
// Features
const midMult = qualMult * 0.4 + 0.6;
if (featuresSection.checkbox('newLighting')?.value()) total += 600 * midMult;
if (featuresSection.checkbox('exhausFan')?.value()) total += 300 * midMult;
if (featuresSection.checkbox('mirror')?.value()) total += 400 * midMult;
if (featuresSection.checkbox('medicine')?.value()) total += 400 * midMult;
if (featuresSection.checkbox('accessories')?.value()) total += 250 * midMult;
if (featuresSection.checkbox('storage')?.value()) total += 800 * midMult;
// Special features
if (specialSection.checkbox('heatedFloor')?.value()) total += 1500;
if (specialSection.checkbox('steamShower')?.value()) total += 4000;
if (specialSection.checkbox('bidet')?.value()) total += 1000;
if (specialSection.checkbox('smartMirror')?.value()) total += 1200;
if (specialSection.checkbox('glassEnclosure')?.value()) total += 3000;
if (specialSection.checkbox('customTile')?.value()) total += 2500;
const travelFee = getTravelFee();
return Math.round(total + travelFee);
},
variant: 'large'
});
});
summarySection.addRow(row => {
row.addTextPanel('rangeNote', {
computedValue: () => {
const level = scopeSection.radioButton('renovationLevel')?.value() || 'full';
const ranges: Record<string, string> = {
'cosmetic': 'Typical range: $2,000 - $5,000',
'partial': 'Typical range: $6,000 - $15,000',
'full': 'Typical range: $15,000 - $35,000',
'luxury': 'Typical range: $30,000 - $75,000+'
};
return ranges[level] || '';
},
customStyles: { 'font-size': '0.9rem', 'color': '#059669' }
});
});
const finalSection = form.addSubform('final', {
title: '🧾 Summary',
isCollapsible: false,
sticky: 'bottom'
});
finalSection.addRow(row => {
row.addPriceDisplay('totalEstimate', {
label: 'Total Estimated Cost',
computedValue: () => {
let total = getBasePrice();
const quality = qualitySection.radioButton('overallQuality')?.value() || 'standard';
const qualMult = qualityMultipliers[quality] || 1;
if (componentsSection.checkbox('newVanity')?.value()) {
const vanityType = componentsSection.dropdown('vanityType')?.value() || 'single';
const vanityCosts: Record<string, number> = {
'single': 1200, 'double': 2400, 'floating': 1800, 'custom': 4500
};
total += (vanityCosts[vanityType] || 1200) * (qualMult * 0.5 + 0.5);
}
if (componentsSection.checkbox('newToilet')?.value()) {
total += 600 * (qualMult * 0.5 + 0.5);
}
if (componentsSection.checkbox('newShower')?.value()) {
const showerType = componentsSection.dropdown('showerType')?.value() || 'tub-shower';
const showerCosts: Record<string, number> = {
'tub-shower': 3000, 'shower-only': 4500, 'walk-in': 8500,
'tub-separate': 12000, 'freestanding': 5500
};
total += (showerCosts[showerType] || 3000) * (qualMult * 0.5 + 0.5);
}
const plumbing = plumbingSection.radioButton('plumbingWork')?.value() || 'minor';
const plumbingCosts: Record<string, number> = {
'none': 0, 'minor': 1000, 'moderate': 3500, 'major': 10000
};
total += plumbingCosts[plumbing] || 0;
const electrical = plumbingSection.radioButton('electricalWork')?.value() || 'minor';
const electricalCosts: Record<string, number> = {
'none': 0, 'minor': 550, 'moderate': 1400, 'major': 3500
};
total += electricalCosts[electrical] || 0;
const midMult = qualMult * 0.4 + 0.6;
if (featuresSection.checkbox('newLighting')?.value()) total += 600 * midMult;
if (featuresSection.checkbox('exhausFan')?.value()) total += 300 * midMult;
if (featuresSection.checkbox('mirror')?.value()) total += 400 * midMult;
if (featuresSection.checkbox('medicine')?.value()) total += 400 * midMult;
if (featuresSection.checkbox('accessories')?.value()) total += 250 * midMult;
if (featuresSection.checkbox('storage')?.value()) total += 800 * midMult;
if (specialSection.checkbox('heatedFloor')?.value()) total += 1500;
if (specialSection.checkbox('steamShower')?.value()) total += 4000;
if (specialSection.checkbox('bidet')?.value()) total += 1000;
if (specialSection.checkbox('smartMirror')?.value()) total += 1200;
if (specialSection.checkbox('glassEnclosure')?.value()) total += 3000;
if (specialSection.checkbox('customTile')?.value()) total += 2500;
const travelFee = getTravelFee();
return Math.round(total + travelFee);
},
variant: 'large'
});
});
finalSection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'Get multiple quotes for accurate pricing.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'font-style': 'italic' }
});
});
form.configureSubmitButton({
label: 'Get Free Consultation'
});
}