Files
helvoirtgevelreiniging.nl/todo.md
T

136 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Helvoirt Gevelreiniging — Bouwplan (todo)
Stack: Astro 6 + Emdash 0.1.0 + Cloudflare Workers (D1 + R2 + KV). Register: **conversiegerichte lokale dienstensite**, familie van `onderhoudsbedrijfvanhelvoirt.nl`. Zie `architecture.md` (technisch) en `CLAUDE.md` (regels/workarounds). **Alleen wijzigen in `dewebsmid\helvoirt`.**
**Status (16-06-2026): marketing-pagina's LIVE + CMS-migratie loopt — morgen verder met LIVE ZETTEN.** Alle pagina's live op `https://helvoirt-gevelreiniging.mike-197.workers.dev` (deploy version **`f576e989`**). **CMS geseed + ingericht** (zie §3); de SEO-partij kan pagina's + locatiepagina's beheren/maken/SEO-optimaliseren (`CMS-SEO-handleiding.md`). **Werkgebied-pills** = Helvoirt + Limburgse steden. **Bestaande pagina's worden "volledig naar CMS" gemigreerd** (klantopdracht), met fallback-veiligheid + sentinel-verificatie: **Over ons, Homepage(-tekst), Contact KLAAR**; **Maastricht-locatiepagina live**.
← **VOLGENDE STAP:** De **code-side go-live-laag is af + live** (17-06-2026, deploy `ed137121`): robots, sitemap (incl. CMS), JSON-LD (LocalBusiness + Service/Breadcrumb), GTM-render, OG-image + apple-touch-icon, middleware, privacy/voorwaarden, **mail-code + D1-opslag** (`contact_submissions`) + **magic-link-plugin**. **CMS = 100% van de pagina's** (Wave 3 Diensten klaar → ook de 5 dienstpagina's + overzicht + homepage-accordion + nav-menu uit de `diensten`-collectie). Wat nu nog rest is **(1) externe input** — mailbox-wachtwoord/host → secret `CONTACT_SMTP_PASS` (bash printf), SEO-partij-e-mail → `users`-rij, klantcontent (NAW/reviews/FAQ/foto's/GTM-ID), domein-DNS → zone+nameservers+`routes`(rebuild)+mail-DNS+HTTPS (zie **`GO-LIVE.md`**) — en **(2) optioneel** self-hosted fonts, Lighthouse-meting, checklist `.xlsx`. **CMS-migratie is 100% klaar** (alle pagina's + alle beelden, tekst én media, in het CMS — deploy `57651519`). Schema op live D1 = via `migrations/*.sql`. Herdeploy: `astro build` + `wrangler deploy`.
---
## ⏳ Open punten (afstemmen met klant)
- [x] **Telefoon + adres aangeleverd:** `085 060 4956`, `Stationsplein 1, 6131 AS Sittard` (in `Base.astro` + `contact.astro` fallbacks; staat live).
- [x] **Werkgebied-copy blijft "Helvoirt en omgeving" (16-06-2026):** kop + alle SEO-copy ongewijzigd ("Helvoirt en omgeving"). **Alleen de pills vernieuwd:** Helvoirt blijft vooraan, daarna de nieuwe steden. Eerdere reverteerde poging om naar "Sittard en omgeving" te gaan is teruggedraaid op klantverzoek.
- [ ] **NAW resterend:** e-mail (nu placeholder `info@helvoirtgevelreiniging.nl`), KvK, BTW, WhatsApp, openingstijden bevestigen, **Google-reviews-URL + score**.
- [ ] **Echte reviews** (i.p.v. de 3 placeholders) — liefst met de Google-link.
- [ ] **Echte foto's**: hero + per dienst (resultaat-galerijen) — nu AI-placeholders + de 6 echte `werk-*`-foto's. (Before/after-paren niet meer nodig: projecten geschrapt.)
- [x] **Werkgebied-plaatsen (pills) vastgesteld:** Helvoirt, Maastricht, Echt, Roermond, Weert, Sittard, Heerlen, Brunssum, Kerkrade, Landgraaf. Bron: klant 16-06-2026. (Kop blijft "Helvoirt en omgeving".)
- [ ] **FAQ-teksten** "Goed om te weten" bevestigen.
- [ ] **Logo:** levert klant een eigen logo aan? (nu tekst-wordmark "Helvoirt / Gevelreiniging" + **H-monogram**-merkmark; druppel verwijderd.)
- [x] ~~Kleuren samplen~~ → uit Elementor-kit zusterbedrijf. ~~Fonts~~ → Archivo Expanded + Open Sans. ~~Worker/D1/R2-naamgeving~~ → `helvoirt-gevelreiniging` / `helvoirt-db` / `helvoirt-media`. ~~Hero-fotografie~~ → nano-banana placeholders (klantkeuze). ~~Blog~~ → buiten launch-scope.
---
## §1 — Scaffold (mirror exterios) ✅ GEDAAN
- [x] `package.json`/`tsconfig`/`astro.config.mjs`/`worker.ts`/`live.config.ts`/`wrangler.jsonc`/`.gitignore`/`.env` + scripts + `lib/img.ts`.
- [x] `npm install --ignore-scripts` (721 packages) · `astro sync`/`check`/`build` slagen.
- **Geleerd:** `NODE_OPTIONS=--use-system-ca` voor elke astro/wrangler-call; placeholder-`database_id` (miniflare eist niet-leeg).
## §2 — Design tokens ✅ GEDAAN (DESIGN.md = optioneel, nog niet geschreven)
- [x] `tokens.css`: committed blauw/petrol palette (cyaan `#4ebfd9`, actie `#0069ff`/`#0055ce`, petrol `#1d4852`/`#15363d`, off-white `#f6fafb`), getinte neutralen, status/ster-tokens.
- [x] Fonts **Archivo Expanded** (kop) + **Open Sans** (body). Géén Poppins/Fraunces.
- [x] `.btn-cta`/`.btn-outline`/`.btn-text`, section-helpers, `.reveal`.
- [ ] (optioneel) `DESIGN.md` schrijven via `/impeccable document`.
## §3 — Emdash schema (seed/seed.json) + CMS voor SEO-partij 🟢 GESEED + LIVE (volledige migratie loopt, §3b)
**Doel (klant 16-06-2026):** de SEO-partij moet zelf **gewone pagina's én locatiepagina's** kunnen aanmaken en optimaliseren voor SEO. Bewust **SEO-vriendelijk + simpel** ingericht (3 onderdelen i.p.v. het volledige sectie-model).
- [x] **`seed/seed.json` geschreven** — collecties: **`site_settings`** (NAW + `address_street`/`address_city` + KvK/BTW/WhatsApp + social + **SEO-standaard meta_title/description** + **GTM** + logo/favicon/og_image), **`paginas`** (gewone pagina's: title/intro/portableText-content + SEO-groep), **`locaties`** (locatiepagina's: plaats/H1/intro/portableText-content + SEO-groep). SEO-groep = expliciete velden met nette NL-labels: `meta_title`, `meta_description`, `canonical`, `og_image`, `noindex`. Demo-content: `site_settings/main` (echte NAW, werkgebied "Helvoirt en omgeving", score 4,9) + 1 concept-sjabloon per type (Voorbeeldpagina + locatie Maastricht).
- [x] **Routes gebouwd** (lezen CMS met try/catch + fallback → nette 404 vóór seed):
- `src/pages/[...slug].astro` — **root-router**: zoekt de slug op in `locaties` (lokale pagina, petrol-hero + content) én `paginas` (gewone pagina); beide op de **root** (`/maastricht`, `/onderhoudstips`). Locatie wint bij botsing. Laagste prioriteit, PortableText, eigen branded 404. (Klantkeuze 16-06: locatiepagina's op `/URL`, niet `/werkgebied/URL`.)
- `src/pages/werkgebied/index.astro` — werkgebied-hub die gepubliceerde locaties oplijst (linkt naar `/<plaats>`); anders statische pills.
- [x] **`src/lib/images.ts`** = `resolveCmsImage` (uit behangerbrabant) voor image-velden; **`Base.astro`** uitgebreid met `noindex`-prop + absolute OG-URL; gedeelde **`.prose`** in `tokens.css`. **`astro check` 0/0/0 + build OK.**
- [x] **`CMS-SEO-handleiding.md`** geschreven voor de SEO-partij (inloggen, pagina/locatie aanmaken, SEO-velden, do's/don'ts, wat nog in code zit).
- [x] **Geseed (16-06-2026):** setup-wizard gedraaid met "Include sample content" → `site_settings`/`paginas`/`locaties` + demo in remote D1.
- [x] **Ingebouwde `pages`/`posts` verwijderd** (emdash-defaults, leeg/ongebruikt) via `scripts/cleanup-default-collections.sql`. Blijven weg (defaults worden alléén bij setup aangemaakt). CMS toont nu alleen relevante onderdelen.
- [x] **`Base.astro` leest nu `site_settings.meta_title/meta_description/og_image`** (per-pagina prop > Instellingen > code-default) → SEO-partij kan site-brede SEO in Instellingen zetten.
- [ ] **CMS-login**: e-mail-provider-plugin (magic-link, newsite.md §6b) + SEO-partij-gebruiker(s) in `users`. (nog te doen)
### §3b — Volledige CMS-migratie bestaande pagina's (klant: "Volledig naar CMS") ✅ KLAAR (17-06-2026) — alle pagina's + álle beelden in het CMS (tekst + media)
**Aanpak (bewezen, risico-arm):** per pagina (a) model in `scripts/cms-models/<x>.mjs`, (b) `scripts/gen-cms-sql.mjs` genereert D1-SQL die emdash's eigen tabelstructuur spiegelt (overgenomen uit de echte `ec_paginas`-DDL; **géén search/FTS** op nieuwe collecties), (c) toepassen op remote D1 via `wrangler d1 execute --file migrations/*.sql`, (d) `.astro` herschrijven om uit CMS te lezen **met de huidige tekst als fallback** (site blijft identiek), (e) deploy + verifiëren.
- [x] **Machinerie gebouwd:** `scripts/gen-cms-sql.mjs` (generator: type→column_type, systeemkolommen, ULID via `ulidx`) + `scripts/cms-models/`. UTF-8 (·, —) blijft intact door de round-trip (geverifieerd).
- [x] **Wave 1 — Over ons KLAAR + LIVE-GEVERIFIEERD** (version `3ce0ce32`). Singleton **`over_ons`** (hero/verhaal/waarden-koppen + SEO; verhaal = portableText) + **`waarden` als inline repeater-veld** op over_ons. `over-ons.astro` leest uit CMS met fallback. Sentinel-test bewees CMS-aansturing; 4/4 repeater-rijen + icoon-SVG renderen live. Visueel identiek.
- [x] **Klantfeedback Over ons verwerkt (`0003_*.sql`):** "Waarden" van losse collectie → **inline repeater** (rijen toevoegen/verwijderen/**verslepen** binnen de pagina, WP-Bakery-achtig); kern-inhoudvelden **verplicht** zodat "(optional)" daar weg is. **Native emdash:** pagina's **dupliceren** (kopieer-icoon in lijst, `POST .../duplicate`), collectie-rijen add/delete. **"(optional)"** is niet globaal uitschakelbaar (geen branding/CSS-hook); alleen via velden verplicht maken. Repeater-velddef = `validation.subFields` (zie `scripts/gen-over-ons-repeater.mjs`).
- [x] **Beeldvelden toegevoegd aan Over ons (`0004_*.sql`):** `hero_image` (banner) + `story_image` (verhaalfoto), via `resolveCmsImage` met de huidige statische foto's als fallback (leeg veld = standaardfoto). Sentinel-test bewees dat het veld de pagina aanstuurt; leeg → fallback. **Les voor volgende waves: beeldvelden meenemen** (hero/galerij/dienst-foto's) — niet alleen tekst.
- [x] **Labels opgeschoond (`0005_*.sql`):** "(~N tekens)" uit alle SEO-labels (site_settings/paginas/locaties/over_ons) — ook in `seed.json` + model. **NB:** "(optional)" blijft (emdash-standaard, niet uitzetbaar).
- [x] **Foto's in de mediabibliotheek gezet (`0006_*.sql` + R2-upload, `scripts/gen-over-ons-media.mjs`):** `hero-after-1280.webp` + `werk-3.webp` naar R2 (`helvoirt-media`, key=`<ulid>.webp`) + `media`-rijen + veld-JSON (local provider). De Over ons-beeldvelden tonen nu de echte foto in het CMS (niet leeg). Geverifieerd: media serveert via `/_emdash/api/media/file/<key>` (200, image/webp) en de pagina gebruikt de CMS-afbeeldingen. **Patroon voor volgende waves:** zo ook de echte foto's voor homepage/diensten in de library zetten.
- [x] **Maastricht-locatiepagina LIVE** (`/maastricht`, gepubliceerd, 200, H1 correct).
- [x] **Wave 2a — Homepage TEKST KLAAR + LIVE-GEVERIFIEERD** (version `1223ddda`). Singleton **`homepage`** (`0007_*.sql`): hero (titel/sub/**vinkjes-repeater**), intro, diensten-koppen, **werkwijze-repeater** (4), bewijs-kop, reviews (uitgelicht + **kleine-reviews-repeater**), werkgebied-koppen, **FAQ-repeater** (4). `Hero.astro` (props) + `index.astro` lezen uit CMS met fallback. Sentinel-test bewees CMS-aansturing; alle repeaters renderen live. Generator uitgebreid met repeater-`validation`/subFields.
- [x] **Wave 2b — Homepage BEELD KLAAR + LIVE-GEVERIFIEERD (17-06-2026, `0011_homepage_media.sql`).** Collecties **`hero_cards`** (3) + **`galerij`** (6) als image-rijen (image+tekst); `Hero.astro` (via `cards`-prop uit `index.astro`) + de bewijs-sectie lezen eruit met fallback. Foto's in de mediabibliotheek (galerij hergebruikt de werk-foto's uit Wave-3-media; hero gebruikt kleine -768-varianten voor perf → 2 nieuwe media). Live: 3/3 hero + 6/6 galerij = CMS-media; screenshot bevestigt visueel identiek.
- [x] **Diensten-BEELD in de mediabibliotheek (`0010_diensten_media.sql`, `gen-diensten-media.mjs`).** 13 unieke foto's naar R2 + `media`-rijen, gekoppeld aan alle **25 beeldvelden** (nav/hero + 3 resultaat-slots × 5 diensten). Live: detailpagina 4/4, overzicht 5/5, accordion 5/5 = CMS-media. CMS serveert één formaat → -1280 voor hero/cards, werk-basis voor galerij; `srcsetFor` geeft `undefined` voor CMS-paden (geen kapotte srcset).
- [x] **Wave 3 — Diensten KLAAR + LIVE-GEVERIFIEERD (17-06-2026, `0009_diensten.sql`).** Collectie **`diensten`** (5 entries, 36 velden) gedeeld door: **homepage-accordion** (`nav_*`), **`/diensten`-overzicht** (`title` + `nav_*`), **`/diensten/[slug]`-detail** (alle velden: hero/points/intro/included/resultaat-galerij/aanpak/faq/seo/noindex) én het **nav-dropdownmenu** (`Base.astro`, op `nav_order`). Alles leest uit CMS met de hardcoded data als fallback. Beeldvelden leeg = code-fallback naar de huidige foto's (`nav_image`/`hero_image`/`result_N_image` per dienst; media-library-populatie = optionele vervolgstap zoals over-ons). Resultaat-galerij = **3 vaste image-slots** (repeater kan geen image-subveld). Sentinel-test bewees dat de CMS-waarden zowel de detailpagina als de homepage-accordion aansturen. Onbekende slug → 404. `astro check` 0.
- [x] **Wave 4 — Contact KLAAR + LIVE-GEVERIFIEERD** (version `f576e989`, `0008_*.sql`). Singleton **`contact_page`** (kicker/H1/intro + SEO). `contact.astro` leest uit CMS met fallback (NAW blijft uit `site_settings`). Sentinel-test OK. Generator is nu argument-gedreven: `node scripts/gen-cms-sql.mjs <model> <out.sql>`.
## §4 — Base layout + kerncomponenten ✅ GEDAAN
- [x] `Base.astro` (donkere petrol header met telefoon + CTA + dropdown, drawer, prefooter-form `#contact`, footer, sticky-CTA, reveal-IO, SEO-head, site_settings try/catch).
- [x] `ContactForm.astro` (naam [placeholder "Naam"], e-mail, telefoon, bericht + honeypot + time-trap).
- [x] `api/contact.ts` (spam-bescherming + validatie + **D1-opslag `contact_submissions` + 2 mails via `src/lib/smtp.ts`**; secret = §12).
- [ ] `CtaBand.astro` extraheren wanneer subpagina's het nodig hebben.
## §5 — Homepage (index.astro) ✅ GEDAAN + GEPOLISHED
- [x] **Hero** = **tekst links + werkfoto-collage rechts** (Tailark hero-6 nagebouwd): Archivo-kop "Je gevel weer als nieuw." (800) + dominante blauwe CTA + vinkjes + prominente 4,9-Google-regel; rechts **3** staande werkfoto-cards die links overlopen in het tekstblok (`.hero__fade`), mobiel swipebaar. Doorlopen: craft → bolder (verworpen) → quieter → split (verworpen) → collage → audit 20/20 → critique 33/40 + fixes. **Géén before/after-slider** in hero (→ planning `/projecten`, §7). `hero-after/before/gevel.webp` + `degrade-before.py` nu ongebruikt op de homepage (de 3 hero-cards gebruiken `werk-1/3/5`; was 4 incl. `werk-6`/Metselwerk, op klantverzoek terug naar 3 + cards verbreed).
- [x] **Intro** (statement + inline meta), **Diensten = uitklappende beeld-accordion** (5 panelen, "+"-indicator, blijft open om te lezen, mobiel gestapeld), **Werkwijze**-tijdlijn (licht), **Bewijs**-galerij (echte foto's), **Reviews** (uitgelicht + 2 klein), **Werkgebied**-pills, **FAQ** "Goed om te weten".
- [x] Impeccable: craft ("het verschil zien") → bolder (donkere header) → overdrive (accordion) → **audit 19/20** (tapdoelen ≥44px, fetchpriority, tokens) → **critique 32/40** (accordion klikbaar, FAQ, scaffold-cleanup, hero→`#contact` + adresveld).
- [x] **`animate`-pass:** samenhangende motion-laag — consistente scroll-reveals over álle secties, werkwijze-tijdlijn tekent zich in (`.flow__line` + `.reveal--draw`), gestaggerde werkgebied-pills + FAQ (`.reveal--stagger`/`--i`), `:active`-indruk op `.btn-cta`/`.btn-outline`, offerteformulier-spinner (`aria-busy`) + geanimeerde succesmelding, trage hero-gloed. Nieuwe token-primitives `.reveal--draw`/`.reveal--stagger` in `tokens.css`. Alles achter `prefers-reduced-motion`.
- **Eerdere diensten-iteraties (verworpen):** zig-zag-banden → index+preview → foto-mozaïek → typografisch menu → **accordion** (definitief).
- [ ] Inline secties later extraheren naar herbruikbare componenten.
## §6 — Diensten ✅ KLAAR (overzicht + 5 detailpagina's)
- [x] **`pages/diensten/[slug].astro`** = herbruikbaar SSR-sjabloon met **alle 5 slugs gevuld** (zie onder); per pagina dienst-hero (+4,9-regel) + intro/checklist + **resultaat-galerij** + mid-page CTA-band + aanpak-stappen + dienst-FAQ → CTA's `#contact`. Onbekende slug → **404** (getest). `astro check` 0 errors, axe-core **0 violations**, geen overflow (1440/768/390), mobiel stapelt netjes.
- [x] **Resultaat-galerij** i.p.v. een voor/na-slider op de dienstpagina (klantkeuze): 3 losse resultaatfoto's met bijschrift per stap (aanslag weg, voegwerk hersteld, strak/verzorgd). Reden: een echte voor/na-slider eist twee exact uitgelijnde foto's van dezelfde gevel, wat de klant zelden heeft. Werkt met losse werkfoto's.
- [x] **`components/BeforeAfter.astro`** = herbruikbare voor/na-sleepvergelijker (range-input → sleep + toetsenbord, `:has()`-focusring, "voor"/"na"-labels). **Nergens geïmporteerd** (projecten geschrapt, §7); bewaard als herbruikbaar component.
- [x] **Alle 5 slugs ingevuld** in `DIENSTEN`: `gevelreiniging`, `voegwerk`, `dakreiniging-coating`, `schoorsteen-voegwerk`, `algemeen-reinigen`. Elk: dienst-hero (+4,9-regel) + intro/checklist + resultaat-galerij + mid-page CTA-band + aanpak-stappen + dienst-FAQ. **Dak** gebruikt de 3 echte dakfoto's als narratief (mos → reinigen op hoogte → schoon, kop "Van mos tot een schoon dak"). Galerijen mixen de echte `werk-*`-foto's met dienst/hero-placeholders.
- [x] **Smoke-test:** alle 5 slugs → **200** (juiste H1), onbekend → **404**. Per nieuwe pagina: axe **clean**, geen overflow (1440/768/390). `astro check` 0 errors, build OK.
- [x] **`pages/diensten/index.astro`** (overzicht, 5 dienstkaarten met "Bekijk" → detail) gebouwd; nav-link "Diensten" werkt nu, breadcrumb-crumb "Diensten" hersteld op de detailpagina's. 200, axe clean, geen overflow.
- [ ] Image-fallback per slug bestaat al (`/images/dienst-<slug>.webp`). Echte projectfoto's vervangen de placeholders zodra de klant ze levert.
- [x] **`/impeccable critique` op `/diensten/gevelreiniging`** → **33/40** (sterk), detector `[]` (0 slop-patronen), cognitive load laag. Fixes doorgevoerd: **P0** alle CTA-knoppen (header/sticky/drawer) → `#contact` i.p.v. `/contact`-404 (globaal in `Base.astro`); **P1** 4,9-Google-trust-regel in de dienst-hero + **mid-page CTA-band** ("Klaar om je gevel weer als nieuw te zien?") na de galerij als desktop-snelkoppeling naar het formulier; **P2** dode breadcrumb-crumb "Diensten" verwijderd tot `/diensten` bestaat; **contrast-fix** stap-nummers cyaan → diep blauw (`--c-action-deep`, cyaan op `--soft` zakte onder WCAG). Galerij bewust gelaten (klantkeuze, foto's later). FAQ bewust plat gehouden (homepage-FAQ is óók plat; accordion zou juist inconsistent zijn). Na de fixes: axe 0, geen overflow (1440/768/390), build OK.
## §7 — Projecten (before/after) ❌ GESCHRAPT (klantverzoek)
- [x] Projecten-pagina geschrapt op klantverzoek: uit de nav (`Base.astro`) en de homepage-link ("Alle projecten") verwijderd. `BeforeAfter.astro` blijft als **ongebruikt** herbruikbaar component staan (nergens geimporteerd, dus niet in de build).
## §8 — Contact / Over-ons / Werkgebied 🟡 DEELS (contact + over-ons af)
- [x] **`pages/contact.astro`** (`hidePrefooter` + NAW + formulier, sectie `id="contact"`). Contactblok = **Bel ons / Mail ons / Adres** (Werkgebied + Bereikbaar op klantverzoek verwijderd) + 4,9-regel links, formulier-kaart rechts; e-mail op één regel (1-koloms). 200, axe clean, geen overflow.
- [x] **`pages/over-ons.astro`** (hero + 4,9-regel + verhaal met werkfoto + 4 waarden + werkgebied-pills). 200, axe clean, geen overflow.
- [x] Hoofd-CTA's (header/sticky/drawer) → `/contact` (was interim `#contact`); sticky-CTA verborgen wanneer `hidePrefooter` (dus op /contact). Naam-veld-placeholder in alle formulieren: "Naam" (was "Voor- en achternaam").
- [ ] Form end-to-end testen (valid / honeypot / time-trap / 422) — na Mailtrap (§12).
- [ ] `pages/werkgebied.astro` (optioneel, lokale SEO) — nog te bepalen.
## §9 — Footer-pagina's + SEO 🟢 GROTENDEELS KLAAR (17-06-2026, deploy `15e076ff`)
- [x] **Branded 404** bestaat nu via de catch-all `[...slug].astro` (onbekende route → nette 404-pagina met CTA i.p.v. blanco). Losse `404.astro` daardoor optioneel.
- [x] **`public/robots.txt`** (Allow /, Disallow /_emdash/ + /api/, Sitemap-regel). Live geverifieerd.
- [x] **`src/pages/sitemap.xml.ts`** = vaste code-routes + gepubliceerde `paginas` + `locaties` (live query, noindex uitgesloten; CMS-fout → vaste routes blijven). **Overschrijft emdash's eigen `/sitemap.xml`** (die gebruikt `/collectie/slug`, fout voor ons root-URL-model) — user-route in `src/pages/` wint (build-warning is verwacht/onschadelijk). Live geverifieerd: bevat `/maastricht` uit CMS + `/privacy`/`/voorwaarden`.
- [x] **`site: "https://helvoirtgevelreiniging.nl"`** in `astro.config.mjs` → canonical + sitemap-base wijzen naar het echte domein (ook vóór koppeling).
- [x] **`voorwaarden.astro`** = Algemene voorwaarden (17-06-2026: herschreven o.b.v. zusterbedrijf onderhoudsbedrijfvanhelvoirt.nl, 10 artikelen; **klant juridisch laten controleren**) + **in het CMS** (singleton `voorwaarden`, artikelen-repeater, `0012_voorwaarden.sql`, sentinel-geverifieerd). **Privacyverklaring op klantverzoek VERWIJDERD** (pagina + footer-link + sitemap; /privacy → 404). NB: voor de AVG is een privacyverklaring bij een contactformulier doorgaans wél aan te raden.
- [x] **JSON-LD `HomeAndConstructionBusiness`** (LocalBusiness-subtype) site-breed in `Base.astro`: naam/desc/url/image/telefoon/e-mail/adres (gesplitst) + areaServed + aggregateRating (4,9) + optioneel kvk/btw/sameAs uit `site_settings`. Live geverifieerd (rendert met CMS-data).
- [x] **GTM-renderblok** in `Base.astro` (head-script + body-noscript), leest `site_settings.gtm`; leeg = niets geïnjecteerd (geverifieerd). + theme-color + twitter-cards + og:image-default.
- [x] **Gebrande 1200×630 OG-image** (`public/images/og-default.webp` via `scripts/make-og.py`: schone gevel + petrol-gradient + wordmark + 4,9-Google) + `og:image:width/height/type` in `Base.astro`. Live (200, image/webp). + **`apple-touch-icon.png`** (180×180 H-monogram) + `<link>`.
- [x] **`Service` + `BreadcrumbList` JSON-LD** op de 5 dienst-detailpagina's (`diensten/[slug].astro`, matcht het zichtbare kruimelpad). 3 ld+json-blokken per dienstpagina (LocalBusiness + Service + Breadcrumb). Live geverifieerd.
- [ ] **Resterend (optioneel):** `FAQPage` JSON-LD (wacht op echte FAQ-teksten — placeholder nu, schema zou dun zijn), self-hosted fonts (Archivo Expanded niet via css2, zie §10/notitie), Lighthouse/CWV-meting (keyless PSI = 429, **eigen PSI-key nodig**).
## §10 — Beeld-pipeline 🟡 DEELS
- [x] nano-banana placeholders: `hero-after/before.webp`, `hero-gevel.webp`, 5× `dienst-*.webp`; echte input → `werk-1..6.webp`; 768/1280/1920-varianten + `IMG_WIDTHS`/`srcsetFor` bedraad. In gebruik: dienst-hero's (`dienst-*`), homepage-hero + galerijen (`werk-*`), dienst-resultaat-galerijen + over-ons (`hero-after/gevel`). Ongebruikt: `hero-before.webp` (alleen via het geparkeerde `BeforeAfter.astro`).
- [ ] Echte foto's van klant verwerken → placeholders vervangen; `IMG_WIDTHS` bijwerken.
## §11 — Cloudflare provisioning + deploy ✅ GEDAAN (preview)
- [x] `wrangler d1 create helvoirt-db` → `database_id` `0fb83f17-f91d-4910-ac8c-80f00569ce35` in `wrangler.jsonc`; `r2 bucket create helvoirt-media`; KV `SESSION` auto-provisioned.
- [x] `astro build` + `wrangler deploy` → **`https://helvoirt-gevelreiniging.mike-197.workers.dev`** (alle marketing-pagina's 200 op fallbacks; onbekend → 404; `/_emdash/admin` → setup). Laatste deploy: version `ff725970`.
- [ ] Remote D1 seeden (dump/restore per tabel, NIET hele DB) + setup-wizard remote — hangt af van §3 (`seed.json`).
## §12 — Domein + Mail + CMS-login 🟡 CODE KLAAR, EXTERNE STAPPEN OPEN (17-06-2026)
- [x] **Mail-code af** (patroon uit exterios/behangerbrabant): `src/lib/smtp.ts` (authenticated SMTP over `cloudflare:sockets`, poort 465) + `api/contact.ts` slaat nu op in **D1 `contact_submissions`** (migratie `0001`, **toegepast op remote D1**) + stuurt **2 mails** (melding-klant + autoreply) via `Promise.allSettled`. **Mailtrap geschrapt** → eigen mailbox-SMTP (besluit conform newsite.md §6). Live geverifieerd: POST → 200, D1-rij opgeslagen (sentinel getest + opgeruimd). **Mail is no-op tot secret gezet is.**
- [x] **CMS-login-code af:** `src/plugins/smtp-email.ts` (email:deliver-provider voor magic-link) + geregistreerd in `astro.config.mjs` (`plugins:[…]`, absoluut pad/forward-slashes). + `src/cloudflare-env.d.ts` (env/sockets types) + `src/middleware.ts` (http→https + www→apex 301, no-op op *.workers.dev).
- [x] **MAIL WERKT (17-06-2026, deploy `cb425dc4`).** `CONTACT_SMTP_PASS` secret gezet (bash printf). **`CONTACT_SMTP_HOST=srv8.dewebsmid.nl`** in `wrangler.jsonc` vars — mailbox draait op srv8 (ontdekt via `/webmail`→roundcube-redirect); `mail.helvoirtgevelreiniging.nl` is NXDOMAIN in Cloudflare-DNS. Live getest: beide mails verstuurd, alle paden (valid/422/honeypot/time-trap) OK, testrijen opgeruimd. Magic-link werkt nu ook (zelfde SMTP).
- [ ] **Mail-DNS in Cloudflare (collega; mijn token mag geen DNS, 403):** A `mail`→srv8-IP (185.84.143.157, DNS-only), MX `@`→mail.helvoirtgevelreiniging.nl, SPF, DKIM (key uit DirectAdmin), DMARC. Nodig voor INKOMENDE mail naar info@ van externen + afleverbaarheid autoreply. Outbound form-mail werkt al zonder.
- [ ] **CMS-gebruiker(s) SEO-partij** in `users` aanmaken (SQL, §6b newsite.md) — e-mailadres(sen) opvragen.
- [x] **DOMEIN LIVE (17-06-2026, deploy `7ed8f33b`).** Zone `helvoirtgevelreiniging.nl` actief op Cloudflare (NS `cesar`/`priscilla.ns.cloudflare.com`). Custom domain (apex + `www`) gekoppeld via `wrangler.jsonc` `routes` + rebuild. **Site live op `https://helvoirtgevelreiniging.nl`** (alle pagina's 200, canonical/sitemap = echte domein, 404 werkt). www→apex + http→https = 301 (middleware). `*.workers.dev` is **uitgeschakeld** (custom-domain-only; geen duplicate content). NS-blokkade was: registrar wees nog naar hosted.nl i.p.v. Cloudflare → collega heeft NS omgezet.
- [ ] **Mail-DNS** (MX/SPF/DKIM/DMARC) — wacht op mailbox (collega) + DNS-edit-token/dashboard (OAuth-token mag geen zone-settings/DNS, gaf 403). **HTTPS-hardening** (Always Use HTTPS + HSTS) = optioneel via dashboard; redirects werken al via middleware, SSL-cert actief (Workers custom domain → SSL-modus n.v.t.).
- [ ] Echte NAW/reviews/werkgebied/FAQ + GTM-ID in `site_settings`; echte foto's.
## §13 — QA & oplevering 🟡 DEELS
- [x] **`/impeccable audit` (hele site) = 17/20 (Good).** Fixes doorgevoerd + live (version `ff725970`): tap-doelen ≥44px (telefoon/e-mail prefooter+contact, logo, skip-link), scrim-kleuren → tokens, dienst-kaarttitels → `<h3>`, eerste-fold-beelden eager+`fetchpriority`, accordion-reflow `contain: layout`. axe 0 op alle 9 pagina's, geen overflow (1440/768/390), `astro check` 0, detector `[]`.
- [ ] Lighthouse/CWV (mobile+desktop) meten, formulier end-to-end (na Mailtrap §12), git-remote + eerste commit/push.
---
## Gedaan (referentie)
- [x] Input gelezen (Content.docx + 6 werkfoto's), referentiesite geanalyseerd, `architecture.md`/`todo.md`/`CLAUDE.md` opgesteld en bijgehouden.