/* Goodman Decorators site — shared shell: header (with mega menu), footer,
section helpers, and the estimate wizard/modal. Composes GDL primitives only. */
const GDL = window.GoodmanDesignLanguageGDL_01a123;
const BIZ = {
name: 'Goodman Painters & Decorators Inc',
brand: 'Goodman',
brandSuffix: 'Painters & Decorators',
phone: '(872) 235-7481',
phoneHref: 'tel:+18722357481',
email: 'goodmandecorators@gmail.com',
address: '2415 W Arthur Ave, Chicago, IL 60645',
rating: '4.9★',
reviews: '43 Google reviews',
since: '2010',
logo: 'assets/goodman-mark.webp',
areas: 'Chicago & the North/Northwest suburbs — Evanston, Skokie, Lincolnwood, Park Ridge, Wilmette, Niles, Morton Grove, Des Plaines',
};
const NAV_LINKS = ['Services', 'Portfolio', 'Locations', 'Contact'];
const NAV_MAP = {
'Services': 'Services.html',
'Portfolio': 'Portfolio.html',
'Locations': 'Locations.html',
'Contact': 'Estimate.html',
'Interior painting': 'Interior Painting.html',
'Exterior painting': 'Services.html',
'Kitchen remodeling': 'Services.html',
'Bathroom remodeling': 'Services.html',
'Basement finishing': 'Services.html',
'Drywall & plaster': 'Services.html',
'Flooring': 'Services.html',
'Cabinet painting': 'Services.html',
'Handyman services': 'Services.html',
'Commercial': 'Services.html',
'About us': 'Home.html',
'Reviews': 'Portfolio.html',
'Get a free estimate': 'Estimate.html',
'Pay an invoice': 'Pay Invoice.html',
'Privacy policy': 'Privacy Policy.html',
'Terms & conditions': 'Terms & Conditions.html',
'Cookie policy': 'Cookie Policy.html',
};
function Eyebrow({ children, style }) {
return {children};
}
function Section({ children, bg, style, id }) {
return ;
}
function SectionHeading({ eyebrow, title, lede, center, maxWidth = 620 }) {
return (
{eyebrow &&
{eyebrow}}
{title}
{lede &&
{lede}
}
);
}
function Breadcrumbs({ trail }) {
return (
);
}
/* ---------- Header with mega menu + real navigation ---------- */
function SiteHeader({ active, onCta }) {
const [menu, setMenu] = React.useState(false);
const [mobileOpen, setMobileOpen] = React.useState(false);
const handleClick = (e) => {
const a = e.target.closest('a');
if (!a) return;
const href = a.getAttribute('href') || '';
if (href.startsWith('tel:') || href.startsWith('mailto:')) return;
if (href && href !== '#') return; // real link — let it navigate
e.preventDefault();
const label = a.textContent.trim();
if (NAV_MAP[label]) { window.location.href = NAV_MAP[label]; return; }
if (a.querySelector('img') || label.startsWith('Goodman')) window.location.href = 'Home.html';
};
const handleOver = (e) => {
const a = e.target.closest('a');
if (a && a.textContent.trim() === 'Services') setMenu(true);
};
return (
setMenu(false)}>
{mobileOpen && (
{ handleClick(e); setMobileOpen(false); }}
style={{ position: 'absolute', top: '100%', left: 0, right: 0, background: 'var(--canvas)', borderBottom: '1px solid var(--border-subtle)', boxShadow: 'var(--elevation-3)', display: 'flex', flexDirection: 'column', padding: '8px var(--gutter) 20px', maxHeight: 'calc(100vh - 76px)', overflowY: 'auto' }}>
{NAV_LINKS.map((l) => (
{l}
))}
{BIZ.phone}
)}
{menu && (
)}
);
}
/* ---------- Footer ---------- */
function SiteFooter() {
const wrapClick = (e) => {
const a = e.target.closest('a');
if (!a) return;
const href = a.getAttribute('href') || '';
if (href.startsWith('tel:') || href.startsWith('mailto:')) return;
if (href && href !== '#') return;
e.preventDefault();
const label = a.textContent.trim();
if (NAV_MAP[label]) window.location.href = NAV_MAP[label];
};
return (
© {new Date().getFullYear()} Goodman Painters & Decorators Inc
);
}
/* ---------- Estimate wizard ---------- */
const WIZARD_SERVICES = ['Interior painting', 'Exterior painting', 'Kitchen remodel', 'Bathroom remodel', 'Basement finishing', 'Cabinet painting', 'Flooring', 'Drywall & plaster', 'Handyman / repairs'];
function EstimateWizard({ onDone }) {
const { Button, Icon, Input, Select, Textarea, Tag } = GDL;
const [step, setStep] = React.useState(0);
const [form, setForm] = React.useState({ service: null, timing: '', address: '', budget: '', details: '', name: '', phone: '', email: '' });
const set = (k) => (v) => setForm((f) => ({ ...f, [k]: v }));
const setInput = (k) => (e) => set(k)(e && e.target ? e.target.value : e);
const steps = ['Your project', 'The details', 'About you'];
return (
{steps.map((s, i) => (
{i > 0 && }
{i < step ? : i + 1}
{s}
))}
{step === 0 && (
What do you need done?
{WIZARD_SERVICES.map((s) => set('service')(s)}>{s})}
)}
{step === 1 && (
} />
)}
{step === 2 && (
)}
{step > 0
?
: }
{step < 2
?
: }
);
}
/* Sends a lead to goodmandecorators@gmail.com via FormSubmit (no backend needed) and,
when an email is provided, auto-replies to the customer. Falls back to mailto if the
request fails (e.g. offline). Note: FormSubmit requires a one-time email confirmation
click by the recipient the first time a new destination address is used. */
async function submitLead(fields, opts) {
opts = opts || {};
const payload = Object.assign({}, fields, {
_subject: opts.subject || 'New message — Goodman Painters & Decorators',
_template: 'table',
_captcha: 'false',
});
if (opts.autorespond && fields.email) payload._autoresponse = opts.autorespond;
try {
const res = await fetch('https://formsubmit.co/ajax/' + encodeURIComponent(BIZ.email), {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error('formsubmit error');
return true;
} catch (e) {
const lines = Object.keys(fields).map((k) => k + ': ' + (fields[k] || '—'));
const a = document.createElement('a');
a.href = 'mailto:' + BIZ.email + '?subject=' + encodeURIComponent(opts.subject || 'New message') + '&body=' + encodeURIComponent(lines.join('\n'));
a.click();
return false;
}
}
/* Builds a mailto: link (fallback / manual use). */
function estimateMailto(form) {
const subject = 'New estimate request' + (form.service ? ' — ' + form.service : '');
const lines = [
'New free-estimate request from goodmandecorators.com',
'',
'Service: ' + (form.service || '—'),
'Timing: ' + (form.timing || '—'),
'Budget: ' + (form.budget || '—'),
'Project address: ' + (form.address || '—'),
'Details: ' + (form.details || '—'),
'',
'Name: ' + (form.name || '—'),
'Phone: ' + (form.phone || '—'),
'Email: ' + (form.email || '—'),
];
return 'mailto:' + BIZ.email + '?subject=' + encodeURIComponent(subject) + '&body=' + encodeURIComponent(lines.join('\n'));
}
/* Hook: shared estimate modal + success toast. Returns [element, open]. */
function useEstimateModal() {
const { Modal, Toast, ToastStack } = GDL;
const [open, setOpen] = React.useState(false);
const [toast, setToast] = React.useState(false);
const submit = (form) => {
setOpen(false); setToast(true); setTimeout(() => setToast(false), 4500);
submitLead(
{ Service: form.service, Timing: form.timing, Budget: form.budget, 'Project address': form.address, Details: form.details, Name: form.name, Phone: form.phone, email: form.email },
{ subject: 'New estimate request' + (form.service ? ' — ' + form.service : ''),
autorespond: 'Thanks for reaching out to Goodman Painters & Decorators. We received your estimate request and will call you within one business day. If it’s urgent, call us at ' + BIZ.phone + '.' }
);
};
const el = (
setOpen(false)} size="md"
title="Request your free estimate"
description="Three quick steps — we'll follow up within one business day.">
{toast && (
setToast(false)} />
)}
);
return [el, () => setOpen(true)];
}
/* Dark closing CTA band */
function CTABand({ onEstimate, title, lede }) {
const { Button } = GDL;
return (
{title || 'Let’s build something you’ll love coming home to.'}
{lede || 'Free estimates, honest timelines, and a team that shows up. Tell us about your project.'}
);
}
Object.assign(window, { GDL, BIZ, Eyebrow, Section, SectionHeading, Breadcrumbs, SiteHeader, SiteFooter, EstimateWizard, useEstimateModal, estimateMailto, submitLead, CTABand });