A static website (no server to run) backed by Supabase: marketing pages, a guided quote builder, booking requests,
and a protected owner dashboard at /admin/.
public/ the website (HTML, CSS, JS) config/site.config.json all business/pricing/scheduling settings
build.mjs builds public/ + config into dist/ supabase/schema.sql the whole database (tables, security, functions)
supabase/schema.sql → Run.
(Safe to run again after updates.)supabase/make-owner.sql and run it in the SQL Editor.config/site.config.json (supabase). The publishable key is
meant to be public; the database only exposes the functions in schema.sql, and the tables are locked.npm run dev # builds and previews at http://localhost:3000 using your real Supabase project
npm run build # writes the deployable site to dist/
Deploy dist/ to any static host. For Netlify: add the GitHub repo, branch supabase; netlify.toml already sets
build command node build.mjs and publish folder dist. Cloudflare Pages works the same way (it reads the generated
_headers file for security headers). After deploying, add the site’s address under Authentication → URL
Configuration → Site URL in Supabase.
| To change… | Edit |
|---|---|
| Business name, phone, tagline, payment methods, minister’s discount line | config/site.config.json → business |
| Prices (per sq ft / linear ft / flat, minimum charge, dirt multipliers) | pricing.services. rate: null shows “Submit for a free personalized quote”. |
| Business hours, time windows, lead time, booking horizon | scheduling (set "placeholder": false once real) |
| Quote-builder services and fields | services; service cards: serviceCards |
| Gallery projects and photos | gallery; put images in public/img/. "compare": true only for same-view photos (enables the slider) |
| Colors | :root in public/css/styles.css |
| FAQ / About copy | public/index.html |
After editing, rebuild/redeploy. Scheduling settings are copied into the database automatically the next time the owner opens the dashboard (you’ll see a notice).
/admin/ the owner accepts, declines, or proposes another time (the customer then accepts/declines on the site).America/Chicago. The public calendar gets only available/unavailable flags, never customer data.npm install # one time (installs a test-only in-process Postgres)
npm test # database tests (access control, validation, overlaps) + a Chrome-driven end-to-end run
The tests never touch your real Supabase project: they run schema.sql in a local Postgres and use a local stand-in for
the Supabase API. Needs Chrome or Edge installed for the browser part.
The site targets WCAG 2.1/2.2 AA: skip link, landmarks and heading order, keyboard operation everywhere (including the photo viewer, calendar and slider), visible focus, labelled form fields with announced errors, alt text on photos, reduced-motion and forced-colors support, and pinch-zoom is never blocked. npm test runs axe-core on every screen the browser test visits and fails on any violation. Automated checks catch only part of the problem: also try the site with a screen reader (NVDA on Windows, VoiceOver on iPhone) and browser zoom at 200%.
notifications.ownerEmail.endpoint in the config): every new quote request, booking request and proposal reply
sends a summary. It is best effort: if Formspree is down the request is still saved and shows in the dashboard.notifications.customerEmail stays false).