- Add 8 new location pages: Cheektowaga, Depew, Grand Island, Hamburg,
Kenmore, Orchard Park, Tonawanda, West Seneca (locations.sqlite)
- Update service slugs to floor-* pattern; repair -> restoration (services.sqlite)
- Replace static sitemap.xml with dynamic sitemap.php (35 URLs, all routes)
- Dynamic footer location list from SQLite (no more hardcoded links)
- Reviews page: schema.org AggregateRating + Review from testimonials.sqlite
- Expand schema.org areaServed to 14 cities on all pages
- Add Elfsight Google Reviews widget to reviews page
- Update blog.sqlite content
- Drop Dockerfile COPY for deleted sitemap.xml
- nginx: route POST /contact/ to contact.php, GET to router.php
- Dockerfile: COPY .env into image
- entrypoint.sh: source .env on container start
- .dockerignore: allow .env to be included in build context
- New api/ Python service (stdlib only — no pip install, no packages):
validates fields server-side, verifies reCAPTCHA, sends via Resend
with idempotency key, rate-limited (5 req/IP/15min)
- Matches existing project tooling (build_locations.py, build_services.py)
- Front-end form.js stays vanilla JS, no JS frameworks anywhere
- docker-compose runs nginx + python:3.13-alpine api with healthcheck
- nginx proxies /api/ to Python service, strips prefix
- Dockerfile now copies only public folders into web root (was
copying everything, exposing /Dockerfile, /build_*.py, /api/.env)
- nginx.conf denies dotfiles, .env, .conf, .yml, .py, .md, .txt
and Dockerfile as defense in depth
- .dockerignore keeps sensitive files out of build context
- .gitignore protects api/.env and __pycache__ from being committed