export function flooringInstallationCalculator(form: FormTs) {
// Material costs per sq ft (material only)
const materialCosts: Record<string, { min: number; max: number; labor: number }> = {
'laminate': { min: 1, max: 5, labor: 3 },
'luxury-vinyl': { min: 3, max: 8, labor: 4 },
'engineered-hardwood': { min: 5, max: 15, labor: 5 },
'solid-hardwood': { min: 8, max: 20, labor: 7 },
'tile-ceramic': { min: 2, max: 8, labor: 8 },
'tile-porcelain': { min: 3, max: 15, labor: 9 },
'natural-stone': { min: 10, max: 50, labor: 12 },
'carpet': { min: 2, max: 12, labor: 2 },
'bamboo': { min: 4, max: 10, labor: 5 },
'cork': { min: 4, max: 12, labor: 5 }
};
// Installation complexity multipliers
const complexityMultipliers: Record<string, number> = {
'simple': 0.9,
'standard': 1.0,
'complex': 1.25,
'premium': 1.5
};
// Subfloor condition adjustments
const subfloorAdjustments: Record<string, number> = {
'good': 0,
'minor-repair': 1.50,
'leveling': 3.00,
'replacement': 8.00
};
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Flooring Installation Estimate',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Service Location Section
const locationSection = form.addSubform('serviceLocation', { title: '📍 Service 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 <= 20) return '📍 Within service area - No travel fee';
if (miles <= 40) return '📍 Extended area - $75 travel fee';
if (miles <= 60) return '📍 Remote area - $150 travel fee';
return '📍 Outside standard area - Please call for availability';
},
customStyles: { 'font-size': '0.9rem', 'color': '#0369a1', 'background': '#e0f2fe', 'padding': '10px', 'border-radius': '6px' }
});
});
// Project Details Section
const projectSection = form.addSubform('projectDetails', { title: '📋 Project Details' });
projectSection.addRow(row => {
row.addDropdown('projectType', {
label: 'Project Type',
options: [
{ id: 'new-install', name: 'New Installation' },
{ id: 'replacement', name: 'Replace Existing Floor' },
{ id: 'refinish', name: 'Refinish Existing Hardwood' }
],
defaultValue: 'replacement',
isRequired: true
}, '1fr');
row.addSlider('totalSqFt', {
label: 'Total Area',
min: 50,
max: 5000,
step: 50,
defaultValue: 500,
showValue: true,
unit: 'sq ft'
}, '1fr');
});
projectSection.addRow(row => {
row.addDropdown('roomType', {
label: 'Primary Room Type',
options: [
{ id: 'living', name: 'Living Room/Family Room' },
{ id: 'bedroom', name: 'Bedroom(s)' },
{ id: 'kitchen', name: 'Kitchen' },
{ id: 'bathroom', name: 'Bathroom' },
{ id: 'basement', name: 'Basement' },
{ id: 'whole-house', name: 'Whole House/Multiple Rooms' },
{ id: 'commercial', name: 'Commercial Space' }
],
defaultValue: 'living'
}, '1fr');
});
// Flooring Material Section
const materialSection = form.addSubform('flooringMaterial', { title: '🪵 Flooring Material' });
materialSection.addRow(row => {
row.addDropdown('flooringType', {
label: 'Flooring Type',
options: [
{ id: 'laminate', name: 'Laminate ($1-5/sq ft material)' },
{ id: 'luxury-vinyl', name: 'Luxury Vinyl Plank/Tile ($3-8/sq ft)' },
{ id: 'engineered-hardwood', name: 'Engineered Hardwood ($5-15/sq ft)' },
{ id: 'solid-hardwood', name: 'Solid Hardwood ($8-20/sq ft)' },
{ id: 'tile-ceramic', name: 'Ceramic Tile ($2-8/sq ft)' },
{ id: 'tile-porcelain', name: 'Porcelain Tile ($3-15/sq ft)' },
{ id: 'natural-stone', name: 'Natural Stone ($10-50/sq ft)' },
{ id: 'carpet', name: 'Carpet ($2-12/sq ft)' },
{ id: 'bamboo', name: 'Bamboo ($4-10/sq ft)' },
{ id: 'cork', name: 'Cork ($4-12/sq ft)' }
],
defaultValue: 'luxury-vinyl',
isRequired: true
}, '1fr');
});
materialSection.addRow(row => {
row.addRadioButton('materialGrade', {
label: 'Material Grade',
options: [
{ id: 'economy', name: 'Economy - Entry level, basic colors/styles' },
{ id: 'standard', name: 'Standard - Good quality, popular styles' },
{ id: 'premium', name: 'Premium - High quality, designer options' },
{ id: 'luxury', name: 'Luxury - Top tier, exotic/specialty' }
],
defaultValue: 'standard',
orientation: 'vertical'
});
});
// Installation Details Section
const installSection = form.addSubform('installDetails', { title: '🔨 Installation Details' });
installSection.addRow(row => {
row.addRadioButton('installComplexity', {
label: 'Installation Complexity',
options: [
{ id: 'simple', name: 'Simple - Square room, no obstacles (-10%)' },
{ id: 'standard', name: 'Standard - Normal room, basic cuts' },
{ id: 'complex', name: 'Complex - Multiple angles, many cuts (+25%)' },
{ id: 'premium', name: 'Premium - Intricate patterns, custom work (+50%)' }
],
defaultValue: 'standard',
orientation: 'vertical'
});
});
installSection.addRow(row => {
row.addDropdown('pattern', {
label: 'Installation Pattern',
options: [
{ id: 'standard', name: 'Standard/Straight Lay' },
{ id: 'diagonal', name: 'Diagonal (+10%)' },
{ id: 'herringbone', name: 'Herringbone (+30%)' },
{ id: 'chevron', name: 'Chevron (+35%)' },
{ id: 'basket-weave', name: 'Basket Weave (+25%)' }
],
defaultValue: 'standard'
}, '1fr');
});
// Subfloor Condition Section
const subfloorSection = form.addSubform('subfloorCondition', { title: '🔍 Subfloor Condition' });
subfloorSection.addRow(row => {
row.addRadioButton('subfloorStatus', {
label: 'Current subfloor condition',
options: [
{ id: 'good', name: 'Good - Level, solid, ready for installation' },
{ id: 'minor-repair', name: 'Minor Repairs - Small fixes needed (+$1.50/sq ft)' },
{ id: 'leveling', name: 'Self-Leveling Needed - Uneven surface (+$3/sq ft)' },
{ id: 'replacement', name: 'Subfloor Replacement - Damaged/rotted (+$8/sq ft)' }
],
defaultValue: 'good',
orientation: 'vertical'
});
});
// Removal & Prep Section (conditional on replacement)
const removalSection = form.addSubform('removal', {
title: '🗑️ Existing Floor Removal',
isVisible: () => projectSection.dropdown('projectType')?.value() === 'replacement'
});
removalSection.addRow(row => {
row.addDropdown('existingFloor', {
label: 'Existing Floor Type',
options: [
{ id: 'carpet', name: 'Carpet (+$1/sq ft)' },
{ id: 'vinyl', name: 'Vinyl/Linoleum (+$1.50/sq ft)' },
{ id: 'laminate', name: 'Laminate (+$1.50/sq ft)' },
{ id: 'hardwood', name: 'Hardwood (+$2.50/sq ft)' },
{ id: 'tile', name: 'Tile (+$4/sq ft)' }
],
defaultValue: 'carpet'
}, '1fr');
row.addCheckbox('disposalIncluded', {
label: 'Include Disposal (+$0.50/sq ft)',
defaultValue: true
}, '1fr');
});
// Additional Services Section
const addonsSection = form.addSubform('addons', { title: '✨ Additional Services' });
addonsSection.addRow(row => {
row.addCheckbox('baseboards', {
label: 'Remove & Reinstall Baseboards (+$2/linear ft)',
defaultValue: true
}, '1fr');
row.addCheckbox('newBaseboards', {
label: 'New Baseboards (+$4/linear ft)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('underlayment', {
label: 'Underlayment/Moisture Barrier (+$0.75/sq ft)',
defaultValue: true
}, '1fr');
row.addCheckbox('transitions', {
label: 'Transition Strips (+$25 each)',
defaultValue: true
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('stairNosing', {
label: 'Stair Nosing (+$30/step)',
defaultValue: false
}, '1fr');
row.addCheckbox('furnitureMove', {
label: 'Furniture Moving (+$100-300)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addSlider('linearFeet', {
label: 'Linear Feet of Baseboards',
min: 20,
max: 500,
step: 10,
defaultValue: 80,
showValue: true,
unit: 'ft',
isVisible: () => addonsSection.checkbox('baseboards')?.value() === true ||
addonsSection.checkbox('newBaseboards')?.value() === true
}, '1fr');
row.addInteger('transitionCount', {
label: 'Number of Transitions',
min: 1,
max: 20,
defaultValue: 3,
isVisible: () => addonsSection.checkbox('transitions')?.value() === true
}, '1fr');
});
addonsSection.addRow(row => {
row.addInteger('stairCount', {
label: 'Number of Stairs',
min: 1,
max: 30,
defaultValue: 12,
isVisible: () => addonsSection.checkbox('stairNosing')?.value() === true
}, '1fr');
row.addDropdown('furnitureAmount', {
label: 'Amount of Furniture',
options: [
{ id: 'light', name: 'Light - Few pieces (+$100)' },
{ id: 'moderate', name: 'Moderate - Normal room (+$200)' },
{ id: 'heavy', name: 'Heavy - Lots of furniture (+$300)' }
],
defaultValue: 'moderate',
isVisible: () => addonsSection.checkbox('furnitureMove')?.value() === true
}, '1fr');
});
// Hardwood Refinishing Section (conditional)
const refinishSection = form.addSubform('refinish', {
title: '✨ Refinishing Options',
isVisible: () => projectSection.dropdown('projectType')?.value() === 'refinish'
});
refinishSection.addRow(row => {
row.addRadioButton('refinishLevel', {
label: 'Refinishing Level',
options: [
{ id: 'screen-coat', name: 'Screen & Recoat ($2/sq ft) - Light refresh' },
{ id: 'full-sand', name: 'Full Sand & Finish ($4/sq ft) - Standard refinish' },
{ id: 'repair-sand', name: 'Repair, Sand & Finish ($6/sq ft) - Fix damage first' }
],
defaultValue: 'full-sand',
orientation: 'vertical'
});
});
refinishSection.addRow(row => {
row.addDropdown('finishType', {
label: 'Finish Type',
options: [
{ id: 'polyurethane', name: 'Polyurethane (Standard)' },
{ id: 'oil-based', name: 'Oil-Based Poly (+$0.50/sq ft)' },
{ id: 'hardwax', name: 'Hardwax Oil (+$1/sq ft)' },
{ id: 'custom-stain', name: 'Custom Stain (+$1.50/sq ft)' }
],
defaultValue: 'polyurethane'
}, '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 <= 20) return 0;
if (miles <= 40) return 75;
if (miles <= 60) return 150;
return 200;
};
// Price Summary Section
const summarySection = form.addSubform('summary', { title: '💰 Your Estimate', isCollapsible: false });
const getMaterialPrice = () => {
const type = materialSection.dropdown('flooringType')?.value() || 'luxury-vinyl';
const grade = materialSection.radioButton('materialGrade')?.value() || 'standard';
const costs = materialCosts[type] || { min: 3, max: 8, labor: 4 };
const gradeMultipliers: Record<string, number> = {
'economy': 0,
'standard': 0.33,
'premium': 0.66,
'luxury': 1
};
const mult = gradeMultipliers[grade] || 0.33;
return costs.min + (costs.max - costs.min) * mult;
};
const getLaborPrice = () => {
const type = materialSection.dropdown('flooringType')?.value() || 'luxury-vinyl';
const costs = materialCosts[type] || { min: 3, max: 8, labor: 4 };
return costs.labor;
};
const getPatternMultiplier = () => {
const pattern = installSection.dropdown('pattern')?.value() || 'standard';
const multipliers: Record<string, number> = {
'standard': 1.0,
'diagonal': 1.10,
'herringbone': 1.30,
'chevron': 1.35,
'basket-weave': 1.25
};
return multipliers[pattern] || 1;
};
summarySection.addRow(row => {
row.addPriceDisplay('materialCost', {
label: 'Material Cost',
computedValue: () => {
if (projectSection.dropdown('projectType')?.value() === 'refinish') return 0;
const sqft = projectSection.slider('totalSqFt')?.value() || 500;
const price = getMaterialPrice();
return Math.round(sqft * price);
},
variant: 'default',
isVisible: () => projectSection.dropdown('projectType')?.value() !== 'refinish'
}, '1fr');
row.addPriceDisplay('laborCost', {
label: 'Installation Labor',
computedValue: () => {
if (projectSection.dropdown('projectType')?.value() === 'refinish') {
const sqft = projectSection.slider('totalSqFt')?.value() || 500;
const level = refinishSection.radioButton('refinishLevel')?.value() || 'full-sand';
const rates: Record<string, number> = { 'screen-coat': 2, 'full-sand': 4, 'repair-sand': 6 };
return sqft * (rates[level] || 4);
}
const sqft = projectSection.slider('totalSqFt')?.value() || 500;
const labor = getLaborPrice();
const complexity = installSection.radioButton('installComplexity')?.value() || 'standard';
const compMult = complexityMultipliers[complexity] || 1;
const patternMult = getPatternMultiplier();
return Math.round(sqft * labor * compMult * patternMult);
},
variant: 'default'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('subfloorCost', {
label: 'Subfloor Work',
computedValue: () => {
const sqft = projectSection.slider('totalSqFt')?.value() || 500;
const status = subfloorSection.radioButton('subfloorStatus')?.value() || 'good';
const rate = subfloorAdjustments[status] || 0;
return Math.round(sqft * rate);
},
variant: 'default',
prefix: '+',
isVisible: () => {
const status = subfloorSection.radioButton('subfloorStatus')?.value();
return status !== 'good';
}
}, '1fr');
row.addPriceDisplay('removalCost', {
label: 'Floor Removal',
computedValue: () => {
if (projectSection.dropdown('projectType')?.value() !== 'replacement') return 0;
const sqft = projectSection.slider('totalSqFt')?.value() || 500;
const existing = removalSection.dropdown('existingFloor')?.value() || 'carpet';
const rates: Record<string, number> = {
'carpet': 1, 'vinyl': 1.5, 'laminate': 1.5, 'hardwood': 2.5, 'tile': 4
};
let total = sqft * (rates[existing] || 1);
if (removalSection.checkbox('disposalIncluded')?.value()) total += sqft * 0.5;
return Math.round(total);
},
variant: 'default',
prefix: '+',
isVisible: () => projectSection.dropdown('projectType')?.value() === 'replacement'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('addonsTotal', {
label: 'Additional Services',
computedValue: () => {
let total = 0;
const sqft = projectSection.slider('totalSqFt')?.value() || 500;
if (addonsSection.checkbox('baseboards')?.value()) {
const lf = addonsSection.slider('linearFeet')?.value() || 80;
total += lf * 2;
}
if (addonsSection.checkbox('newBaseboards')?.value()) {
const lf = addonsSection.slider('linearFeet')?.value() || 80;
total += lf * 4;
}
if (addonsSection.checkbox('underlayment')?.value()) {
total += sqft * 0.75;
}
if (addonsSection.checkbox('transitions')?.value()) {
const count = addonsSection.integer('transitionCount')?.value() || 3;
total += count * 25;
}
if (addonsSection.checkbox('stairNosing')?.value()) {
const stairs = addonsSection.integer('stairCount')?.value() || 12;
total += stairs * 30;
}
if (addonsSection.checkbox('furnitureMove')?.value()) {
const amount = addonsSection.dropdown('furnitureAmount')?.value() || 'moderate';
const costs: Record<string, number> = { 'light': 100, 'moderate': 200, 'heavy': 300 };
total += costs[amount] || 200;
}
// Refinish finish type
if (projectSection.dropdown('projectType')?.value() === 'refinish') {
const finish = refinishSection.dropdown('finishType')?.value() || 'polyurethane';
const addCosts: Record<string, number> = {
'polyurethane': 0, 'oil-based': 0.5, 'hardwax': 1, 'custom-stain': 1.5
};
total += sqft * (addCosts[finish] || 0);
}
return Math.round(total);
},
variant: 'default',
prefix: '+'
});
});
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: () => {
const sqft = projectSection.slider('totalSqFt')?.value() || 500;
let total = 0;
// Material
if (projectSection.dropdown('projectType')?.value() !== 'refinish') {
total += sqft * getMaterialPrice();
}
// Labor
if (projectSection.dropdown('projectType')?.value() === 'refinish') {
const level = refinishSection.radioButton('refinishLevel')?.value() || 'full-sand';
const rates: Record<string, number> = { 'screen-coat': 2, 'full-sand': 4, 'repair-sand': 6 };
total += sqft * (rates[level] || 4);
} else {
const labor = getLaborPrice();
const complexity = installSection.radioButton('installComplexity')?.value() || 'standard';
const compMult = complexityMultipliers[complexity] || 1;
const patternMult = getPatternMultiplier();
total += sqft * labor * compMult * patternMult;
}
// Subfloor
const status = subfloorSection.radioButton('subfloorStatus')?.value() || 'good';
total += sqft * (subfloorAdjustments[status] || 0);
// Removal
if (projectSection.dropdown('projectType')?.value() === 'replacement') {
const existing = removalSection.dropdown('existingFloor')?.value() || 'carpet';
const rates: Record<string, number> = {
'carpet': 1, 'vinyl': 1.5, 'laminate': 1.5, 'hardwood': 2.5, 'tile': 4
};
total += sqft * (rates[existing] || 1);
if (removalSection.checkbox('disposalIncluded')?.value()) total += sqft * 0.5;
}
// Add-ons
if (addonsSection.checkbox('baseboards')?.value()) {
const lf = addonsSection.slider('linearFeet')?.value() || 80;
total += lf * 2;
}
if (addonsSection.checkbox('newBaseboards')?.value()) {
const lf = addonsSection.slider('linearFeet')?.value() || 80;
total += lf * 4;
}
if (addonsSection.checkbox('underlayment')?.value()) total += sqft * 0.75;
if (addonsSection.checkbox('transitions')?.value()) {
const count = addonsSection.integer('transitionCount')?.value() || 3;
total += count * 25;
}
if (addonsSection.checkbox('stairNosing')?.value()) {
const stairs = addonsSection.integer('stairCount')?.value() || 12;
total += stairs * 30;
}
if (addonsSection.checkbox('furnitureMove')?.value()) {
const amount = addonsSection.dropdown('furnitureAmount')?.value() || 'moderate';
const costs: Record<string, number> = { 'light': 100, 'moderate': 200, 'heavy': 300 };
total += costs[amount] || 200;
}
if (projectSection.dropdown('projectType')?.value() === 'refinish') {
const finish = refinishSection.dropdown('finishType')?.value() || 'polyurethane';
const addCosts: Record<string, number> = {
'polyurethane': 0, 'oil-based': 0.5, 'hardwax': 1, 'custom-stain': 1.5
};
total += sqft * (addCosts[finish] || 0);
}
const travelFee = getTravelFee();
return Math.round(total + travelFee);
},
variant: 'large'
});
});
summarySection.addRow(row => {
row.addPriceDisplay('pricePerSqFt', {
label: 'Price Per Square Foot',
computedValue: () => {
const sqft = projectSection.slider('totalSqFt')?.value() || 500;
// Recalculate total
let total = 0;
if (projectSection.dropdown('projectType')?.value() !== 'refinish') {
total += sqft * getMaterialPrice();
}
if (projectSection.dropdown('projectType')?.value() === 'refinish') {
const level = refinishSection.radioButton('refinishLevel')?.value() || 'full-sand';
const rates: Record<string, number> = { 'screen-coat': 2, 'full-sand': 4, 'repair-sand': 6 };
total += sqft * (rates[level] || 4);
} else {
const labor = getLaborPrice();
const complexity = installSection.radioButton('installComplexity')?.value() || 'standard';
const compMult = complexityMultipliers[complexity] || 1;
const patternMult = getPatternMultiplier();
total += sqft * labor * compMult * patternMult;
}
const status = subfloorSection.radioButton('subfloorStatus')?.value() || 'good';
total += sqft * (subfloorAdjustments[status] || 0);
return Math.round((total / sqft) * 100) / 100;
},
variant: 'highlight',
suffix: '/sq ft'
});
});
const finalSection = form.addSubform('final', {
title: '🧾 Summary',
isCollapsible: false,
sticky: 'bottom'
});
finalSection.addRow(row => {
row.addPriceDisplay('totalEstimate', {
label: 'Total Estimated Cost',
computedValue: () => {
const sqft = projectSection.slider('totalSqFt')?.value() || 500;
let total = 0;
if (projectSection.dropdown('projectType')?.value() !== 'refinish') {
total += sqft * getMaterialPrice();
}
if (projectSection.dropdown('projectType')?.value() === 'refinish') {
const level = refinishSection.radioButton('refinishLevel')?.value() || 'full-sand';
const rates: Record<string, number> = { 'screen-coat': 2, 'full-sand': 4, 'repair-sand': 6 };
total += sqft * (rates[level] || 4);
} else {
const labor = getLaborPrice();
const complexity = installSection.radioButton('installComplexity')?.value() || 'standard';
const compMult = complexityMultipliers[complexity] || 1;
const patternMult = getPatternMultiplier();
total += sqft * labor * compMult * patternMult;
}
const status = subfloorSection.radioButton('subfloorStatus')?.value() || 'good';
total += sqft * (subfloorAdjustments[status] || 0);
if (projectSection.dropdown('projectType')?.value() === 'replacement') {
const existing = removalSection.dropdown('existingFloor')?.value() || 'carpet';
const rates: Record<string, number> = {
'carpet': 1, 'vinyl': 1.5, 'laminate': 1.5, 'hardwood': 2.5, 'tile': 4
};
total += sqft * (rates[existing] || 1);
if (removalSection.checkbox('disposalIncluded')?.value()) total += sqft * 0.5;
}
if (addonsSection.checkbox('baseboards')?.value()) {
const lf = addonsSection.slider('linearFeet')?.value() || 80;
total += lf * 2;
}
if (addonsSection.checkbox('newBaseboards')?.value()) {
const lf = addonsSection.slider('linearFeet')?.value() || 80;
total += lf * 4;
}
if (addonsSection.checkbox('underlayment')?.value()) total += sqft * 0.75;
if (addonsSection.checkbox('transitions')?.value()) {
const count = addonsSection.integer('transitionCount')?.value() || 3;
total += count * 25;
}
if (addonsSection.checkbox('stairNosing')?.value()) {
const stairs = addonsSection.integer('stairCount')?.value() || 12;
total += stairs * 30;
}
if (addonsSection.checkbox('furnitureMove')?.value()) {
const amount = addonsSection.dropdown('furnitureAmount')?.value() || 'moderate';
const costs: Record<string, number> = { 'light': 100, 'moderate': 200, 'heavy': 300 };
total += costs[amount] || 200;
}
const travelFee = getTravelFee();
return Math.round(total + travelFee);
},
variant: 'large'
});
});
finalSection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'Final price after in-home measurement.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'font-style': 'italic' }
});
});
form.configureSubmitButton({
label: 'Schedule Free Estimate'
});
}