bmr_bluecap 0f769d7bdd feat(contact): wire the form to a real endpoint at build time
The contact form could not be made to work by configuration. Two reasons,
both fixed here.

The endpoint never reached the browser. k8s/configmap.yaml carried
PUBLIC_CONTACT_FORM_ENDPOINT, but the Deployment has no env/envFrom pointing
at that ConfigMap, so it was dead config. Wiring it up would not have helped
either: this is a static Astro build, so import.meta.env.PUBLIC_* is inlined
when the image is built and the pod only serves pre-built HTML. Anything the
browser needs has to arrive as a build arg. Add ARG/ENV to the Dockerfile and
pass --build-arg from CI, sourced from a CONTACT_FORM_ENDPOINT Actions
secret. Rewrite the ConfigMap as documentation of what the build expects,
with an explicit warning not to add envFrom.

Submission behaviour was also wrong for visitors. A native POST would have
navigated them to the form provider's page; post in the background with
fetch() and send them to our own /thank-you/ instead. On failure the form now
stays put, re-enables the button and offers the phone and email fallback,
rather than silently losing the inquiry. Add a honeypot (_gotcha, which
Formspree treats as spam) positioned off-screen rather than display:none, so
bots that skip hidden fields still fill it.

Read data-endpoint at submit time rather than caching it at init, keeping the
attribute the single source of truth.

Tests: replace the old contact assertion, which faked a native GET
navigation, with one that mocks the endpoint and exercises the real fetch
path including the posted payload. Add a case for endpoint failure.

typecheck 0/0/0, build 0/0, seo:lint clean on 20 pages, e2e 44 passed.
The 4 remaining failures are the pre-existing stale nav assertions in
docs/launch-checklist.md section 2.1, untouched here.
2026-10-09 07:04:59 -04:00

BlueCap Strategies Website

Static marketing site for BlueCap Strategies, built with Astro and deployed to a k3s homelab cluster via GitOps (ArgoCD).

Stack

  • Astro 5 — static output (output: "static"), no runtime JS framework.
  • Content Collections (Markdown) — page copy lives in src/content/ (services, focus-areas, pages, insights), validated by a Zod SEO schema at build time.
  • nginx (Alpine) serves the built dist/ from a multi-stage Docker image.
  • ArgoCD + Kustomize (k8s/) reconcile the deployment; images are published to GHCR by CI.

Local development

npm install
npm run dev          # dev server at http://localhost:4321
npm run build        # astro check (typecheck) + astro build → dist/
npm run preview      # serve the production build
npm run seo:lint     # structural SEO checks over dist/ (run after build)
npm run test:e2e     # Playwright end-to-end tests

Copy .env.example to .env and fill in values as needed (site URL, contact form endpoint, analytics keys). Never commit .env.

Editing content

Page copy is Markdown with typed frontmatter — no code changes needed to edit words:

  • src/content/services/*.md — service detail pages
  • src/content/focus-areas/*.md — focus-area detail pages
  • src/content/pages/*.md — standalone long-form pages (e.g. About)
  • src/content/insights/*.md — blog/insights posts

Frontmatter is enforced by the Zod schema in src/content.config.ts (e.g. description must be 120–158 chars), so the build fails fast on SEO violations. The .md files are build-time source: Astro compiles them to static HTML in dist/. The Markdown itself is never served.

Project layout

src/            Astro pages, layouts, components, content collections, styles
public/         Static assets served as-is (images, robots.txt, healthz)
scripts/        Build tooling (seo-lint.mjs)
tests/          Playwright e2e specs
deploy/         nginx.conf for the runtime image
k8s/            Kustomize manifests (namespace, deployment, service, ingress, configmap)
docs/           Committed engineering records — ADRs, deployment, SEO/AEO, deviations
Dockerfile      Multi-stage build → nginx image containing only dist/

planning/ (git-ignored) holds working docs — content inventories, parity checklists, and the original rebuild brief. See docs/adr/ for the decision record.

Deployment

Push to main → GitHub Actions runs the quality gates, builds and pushes a <git-sha>-tagged image to GHCR, and updates the image tag in k8s/. ArgoCD reconciles the change to k3s. GitHub never receives cluster credentials. See docs/gitops-deployment-strategy.md.

S
Description
No description provided
Readme
13 MiB
0 Stars 1 Watchers 0 Forks
Languages
Astro 46.8%
CSS 25.1%
TypeScript 16.6%
JavaScript 10.1%
Dockerfile 1.4%