export function instantQuoteForm(form: FormTs) {
// Everything that differs per project type lives in one place
const website = {
title: '🌐 Website — Instant Quote',
base: 1200, sliderId: 'pages', included: 1, perUnit: 180, weeksPer: 5,
features: [
{ id: 'seo', name: '🔍 SEO', price: 300 },
{ id: 'cms', name: '📝 CMS', price: 450 },
{ id: 'blog', name: '✍️ Blog', price: 250 },
{ id: 'booking', name: '📅 Booking', price: 600 },
{ id: 'multilang', name: '🌍 Multilingual', price: 400 }
],
extrasLabel: '🎨 Design package',
extras: [
{ id: 'template', name: 'Template — included', price: 0 },
{ id: 'custom', name: 'Custom design (+$800)', price: 800 },
{ id: 'brand', name: 'Full brand kit (+$1,500)', price: 1500 }
]
};
const shop = {
title: '🛒 Online Store — Instant Quote',
base: 2800, sliderId: 'products', included: 0, perUnit: 4, weeksPer: 100,
features: [
{ id: 'subscriptions', name: '🔁 Subscriptions', price: 700 },
{ id: 'reviews', name: '⭐ Reviews', price: 300 },
{ id: 'discounts', name: '🏷️ Discount codes', price: 250 },
{ id: 'inventory', name: '📦 Inventory sync', price: 650 },
{ id: 'loyalty', name: '💎 Loyalty program', price: 500 }
],
extrasLabel: '🚚 Shipping setup',
extras: [
{ id: 'manual', name: 'Manual labels — included', price: 0 },
{ id: 'courier', name: 'Courier API (+$350)', price: 350 },
{ id: 'fulfillment', name: 'Full fulfillment (+$900)', price: 900 }
]
};
const app = {
title: '📱 Web App — Instant Quote',
base: 4500, sliderId: 'screens', included: 3, perUnit: 220, weeksPer: 6,
features: [
{ id: 'auth', name: '🔐 User accounts', price: 600 },
{ id: 'push', name: '🔔 Notifications', price: 450 },
{ id: 'payments', name: '💳 Payments', price: 550 },
{ id: 'analytics', name: '📊 Analytics', price: 350 },
{ id: 'ai', name: '🤖 AI assistant', price: 900 }
],
extrasLabel: '📲 Platforms',
extras: [
{ id: 'web', name: 'Web only — included', price: 0 },
{ id: 'pwa', name: '+ Mobile PWA (+$700)', price: 700 },
{ id: 'native', name: '+ Native wrapper (+$1,200)', price: 1200 }
]
};
const pages = form.addPages('steps', { heightMode: 'tallest-page' });
// Step 1 — what are we building and how big is it?
const project = pages.addPage('project', { mobileBreakpoint: 430 });
const projectType = () => project.radioButton('projectType')?.value() ?? 'website';
const cfg = () => {
const t = projectType();
return t === 'shop' ? shop : t === 'app' ? app : website;
};
// Even the title reacts to your answers
form.setTitle(() => cfg().title);
project.addRow(row => {
row.addRadioButton('projectType', {
label: 'What are you building?',
defaultValue: 'website',
isRequired: true,
orientation: 'horizontal',
options: [
{ id: 'website', name: '🌐 Website' },
{ id: 'shop', name: '🛒 Online Store' },
{ id: 'app', name: '📱 Web App' }
]
});
});
// One slider per project type — only the relevant one is visible
project.addRow(row => {
row.addSlider('pages', {
label: 'How many pages?', unit: 'pages',
min: 1, max: 20, defaultValue: 5,
isVisible: () => projectType() === 'website'
});
});
project.addRow(row => {
row.addSlider('products', {
label: 'How many products?', unit: 'products',
min: 10, max: 500, step: 10, defaultValue: 50,
isVisible: () => projectType() === 'shop'
});
});
project.addRow(row => {
row.addSlider('screens', {
label: 'How many screens?', unit: 'screens',
min: 3, max: 30, defaultValue: 8,
isVisible: () => projectType() === 'app'
});
});
// Step 2 — features and extras (options follow the project type)
const details = pages.addPage('details', { mobileBreakpoint: 430 });
details.addRow(row => {
row.addSuggestionChips('features', {
label: 'Pick your features',
suggestions: () => cfg().features.map(f => ({ id: f.id, name: f.name }))
});
});
details.addRow(row => {
row.addDropdown('extra', {
label: () => cfg().extrasLabel,
placeholder: 'Choose an option…',
options: () => cfg().extras.map(e => ({ id: e.id, name: e.name }))
});
});
// Step 3 — kickoff, timing and contact details
const kickoff = pages.addPage('kickoff', { mobileBreakpoint: 430 });
kickoff.addRow(row => {
row.addDatepicker('startDate', {
label: '🗓️ Preferred kickoff date',
minDate: () => new Date().toISOString().slice(0, 10),
tooltip: 'We can start as early as today'
});
});
kickoff.addRow(row => {
row.addCheckbox('rush', {
label: '⚡ Rush delivery (+25%)',
tooltip: 'Cut the timeline in half'
});
});
kickoff.addRow(row => {
row.addTextbox('fullName', {
label: '👤 Your name',
placeholder: 'Ada Lovelace',
isRequired: true
});
row.addEmail('email', {
label: '📧 Email for the quote',
placeholder: 'ada@example.com',
isRequired: true
});
});
// The pricing engine — plain TypeScript, recalculates on every change
const quote = form.computedValue(() => {
const c = cfg();
const units = project.slider(c.sliderId)?.value() ?? c.included;
const selected = details.suggestionChips('features')?.value() ?? [];
const extraId = details.dropdown('extra')?.value() ?? '';
const rush = kickoff.checkbox('rush')?.value() ?? false;
let price = c.base + Math.max(0, units - c.included) * c.perUnit;
for (const f of c.features) if (selected.includes(f.id)) price += f.price;
price += c.extras.find(e => e.id === extraId)?.price ?? 0;
if (rush) price *= 1.25;
// 3+ features unlock a 10% bundle discount
const picked = c.features.filter(f => selected.includes(f.id)).length;
const hasBundle = picked >= 3;
const finalPrice = hasBundle ? price * 0.9 : price;
const baseWeeks = 2 + Math.ceil(units / c.weeksPer) + Math.ceil(picked / 2);
const weeks = rush ? Math.max(1, Math.ceil(baseWeeks / 2)) : baseWeeks;
return { price, finalPrice, hasBundle, weeks, picked, savings: price - finalPrice };
});
// Live summary lives OUTSIDE the pages — always visible while you navigate
form.addRow(row => {
row.addPriceDisplay('estimate', {
label: 'Your estimate',
variant: 'large',
computedValue: () => quote().finalPrice,
originalPrice: () => quote().hasBundle ? quote().price : null
}, '3fr');
row.addTextPanel('delivery', {
label: 'Timeline',
computedValue: () => {
const q = quote();
const weeksLabel = `~${q.weeks} week${q.weeks > 1 ? 's' : ''}`;
const start = kickoff.datepicker('startDate')?.value();
if (!start) return `📦 Ready in ${weeksLabel}`;
const launch = new Date(start);
launch.setDate(launch.getDate() + q.weeks * 7);
const day = launch.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
return `🚀 ${weeksLabel} — launch ≈ ${day}`;
}
}, '2fr');
});
// The form talks back — nudges you toward the discount
form.addRow(row => {
row.addTextPanel('hint', {
computedValue: () => {
const q = quote();
if (q.hasBundle) return `🎉 Bundle discount unlocked — you save $${q.savings.toFixed(0)}!`;
const left = 3 - q.picked;
return left === 3
? '💡 Pick 3 features to unlock a 10% bundle discount'
: `💡 Only ${left} more feature${left > 1 ? 's' : ''} to a 10% discount!`;
},
customStyles: () => quote().hasBundle
? { color: '#047857', background: '#ecfdf5', padding: '12px 16px', borderRadius: '10px', fontWeight: '600' }
: { color: '#1d4ed8', background: '#eff6ff', padding: '12px 16px', borderRadius: '10px', fontWeight: '500' }
});
});
form.configureSubmitButton({ label: 'Get My Quote' });
form.configureCompletionScreen({
type: 'text',
title: '🎉 Quote on its way!',
message: 'We will get back to you within one business day.'
});
}