Static Site Ship
Ship a redesigned or new public-facing static website end-to-end. The loop is:
design → build → validate → deploy to VPS mirror → smoke-test nginx + backend → handoff report → user ping.
This skill assumes the deliverable is a plain-HTML/CSS/JS site (no build step) deployed to a VPS static mirror under /var/www/<domain>/ (or equivalent) served by nginx. The mirror may or may not be the production host — sometimes it's a fallback while Vercel/Netlify handles the live domain.
When to load
Load this skill when the user asks to:
- redesign a foundation / nonprofit / research / education / public-good website
- publish a new landing page, manifesto page, or project site
- ship a privacy-by-default site (consent-first forms, no third-party tracking)
- deploy static HTML/CSS/JS to a VPS mirror at a known domain
- produce a handoff report for an operator to redeploy on Vercel/Netlify
Do NOT load for: SPA / SSR app deploys (use the framework-specific skill); pure-design mockups without deploy (use claude-design); token-spec authoring (use design-md).
Operating loop (10 steps)
Read the brief completely. Brand identity, mission, values, strategic themes, palette, typography, constraints. Note any "do not claim X" rules (e.g. "do not claim 501(c)(3) until incorporated").
Survey what already exists on the VPS and in any related repo. Read the brand file, the current site files, the express/server capture service if any, the nginx config for the domain, and any archives. Build a mental model of the deploy topology BEFORE writing any HTML.
Confirm scope and tone with the user only if truly ambiguous. If the user has given enough direction (palette, themes, constraints), do NOT ask an A/B/C menu — ship it. Default behavior is to ship. Ask only when the gap materially changes the deliverable.
Design the system, then build the pages.
- Palette + type stack + spacing + radii as CSS variables in one
gbf.css-style file.
- Logo mark as inline SVG (no raster). Sacred-scientific / geometric / emblem-style works well for foundation sites.
- 7–9 pages: index, about, themes (or strategic areas), participate, interest form, thanks, faq, privacy, 404.
- "Forming" / "seeks" / "explores" language until the org is formally established. Avoid: 501(c)(3) claim, named board members, confirmed donors, named partners, confirmed events, tax-deductible status. Use explicit "not tax-deductible" disclaimers where relevant.
Preserve or improve the existing interest-capture flow. Posts JSON to /api/interest (Vercel/serverless). Falls back to POST /interest for VPS express capture. Use consent-first (required consentToContact checkbox), hidden honeypot for bots, no third-party tracking, no analytics pixels.
Validate locally before deploy. Run a script that:
- walks every
href= and src= and confirms internal targets resolve
- confirms all
var(--xxx) CSS references are defined
- confirms every form-field name in the HTML is read by the JS handler
- audits text for forbidden claims (501(c)(3), named donors, tax-deductible-as-fact, etc.)
See
references/validation-script.md for the template.
Deploy to the VPS mirror. Backup the existing mirror first, then cp -a the new tree over it. Preserve any files you don't know about (find … -mindepth 1 -delete is wrong if there's something custom).
Smoke-test the live mirror — the gotcha: nginx serves name-based virtual hosts, so curl http://127.0.0.1/... returns the default vhost (404), not the mirror's vhost. Always pass -H "Host: <domain>":
curl -sS -H "Host: guardianbuilder.org" \
-o /dev/null -w "%{http_code}\n" http://127.0.0.1/
Walk every page and every asset. Then test the backend capture service directly (port from nginx config proxy_pass line):
curl -sS -X POST http://127.0.0.1:<port>/api/interest \
-H "Content-Type: application/json" \
-d '{"name":"...","email":"...","interestAreas":[...],"consentToContact":true}'
See references/deploy-ritual.md for the full sequence with expected outputs.
Build the deployable package. A clean dist/ tree (HTML + css/js/assets, no dev-time README) AND a zipped archive. Use Python's zipfile if zip CLI is missing.
Write the handoff report to /root/HermesVault/<Project>-Redesign-YYYY-MM-DD.md. Sections: what shipped (file table with bytes), design choices, validation results, deployment status, blockers, next actions for the user. End with a short Telegram-style summary in the chat reply.
Status-check discipline (most important pitfall)
Never trust a session_log / session_search snippet as evidence of on-disk state. The session log can be truncated mid-stream; the agent may have shipped far more than the last visible tool call shows. Before answering any "where are we?" or "what's the status?" question:
ls -la <project-dir> to see actual files and timestamps.
wc -c <project-dir>/*.html to confirm file sizes.
- Cross-check against the session log; flag mismatches openly.
If the on-disk state is further along than the session log shows, say so and correct your prior summary. Don't double-down on the stale read.
Also: when the user asks "where are we?" mid-stream, don't ask an A/B menu of next actions. State the verified status, name what's left, and propose to ship. The user picks "ship it now" by default.
Pitfalls
- Nginx Host header gotcha —
curl http://127.0.0.1/ returns the default vhost, not yours. Always -H "Host: <domain>" for local smoke tests.
- Backup the mirror before
find -mindepth 1 -delete — there may be files the redesign doesn't know about. cp -a first, then rebuild.
- Form-field drift — the HTML form names and the JS
data.get(...) calls must match. Run a consistency check.
var(--xxx) drift — defined-but-unused is fine; used-but-undefined is a bug. Scan with a regex.
- False-claim bleed — drafting in foundation/education tone invites invented credentials. Audit against a forbidden-phrase list and require explicit "not Y" disclaimers.
- JS reads multi-value fields with
data.getAll(), not data.get() — easy to miss in the field-consistency check.
zip CLI is often missing on minimal VPS images. Use Python's zipfile module.
/api/* proxy_pass — nginx forwards to the express service on a local port. The proxy_pass line in the nginx site config tells you the port; smoke-test that port directly to verify the backend works independent of nginx.
Templates
templates/interest-form.html — consent-first interest form scaffold (honeypot, areas fieldset, consent checkbox, status region, privacy note). Copy and rename fields.
templates/interest-form.js — submit handler with /api/interest primary + /interest fallback, honeypot trap, status states.
References
references/deploy-ritual.md — the exact deploy + smoke-test sequence with real shell output examples (Host header, nginx config discovery, backend round-trip).
references/validation-script.md — the Python validator that walks links, vars, and form fields.
references/handoff-report-template.md — the report skeleton used for /root/HermesVault/<Project>-Redesign-YYYY-MM-DD.md.
Verification before claiming done
The site is NOT shipped until ALL of the following are true:
- All planned pages exist on disk at the expected sizes.
- Validator reports zero broken links, zero missing assets, zero CSS var drift, zero false claims.
- VPS mirror returns HTTP 200 for every page and every asset (with the correct Host header).
- Backend capture service returns success for a smoke-test POST.
- Handoff report exists at
/root/HermesVault/....
- Telegram-style summary in the chat reply.
If any one of those fails, say so explicitly. Don't paper over with "should work" claims.
Final response shape
Short. Plain text. No markdown headers in the chat reply — this is a CLI terminal.
Include:
- one-line status ("shipped" / "blocked on X" / "degraded: Y works, Z doesn't")
- the deployable artifact path
- the handoff-report path
- any next action that needs the user
1---2name: static-site-ship3description: Ship a redesigned (or new) static website end-to-end on a VPS — design the site, build it, validate it, deploy to a static mirror under /var/www or equivalent, smoke-test nginx (with the right Host header) AND the backend capture service, write a handoff report, and ping the user. Load when the user asks to "redesign", "build", "ship", "deploy", or "publish" a website, especially a research/education/foundation/nonprofit public-facing site where privacy-by-default, consent-first forms, and "no false claims" discipline matter. Skip for SPA / SSR app deploys (Next.js, Vite, etc.) — those have their own build pipelines. Skip for hosted-platform-only deploys (Vercel/Netlify/Cloudflare Pages with no VPS mirror).4license: MIT5---67# Static Site Ship89Ship a redesigned or new public-facing static website end-to-end. The loop is:10**design → build → validate → deploy to VPS mirror → smoke-test nginx + backend → handoff report → user ping.**1112This skill assumes the deliverable is a plain-HTML/CSS/JS site (no build step) deployed to a VPS static mirror under `/var/www/<domain>/` (or equivalent) served by nginx. The mirror may or may not be the production host — sometimes it's a fallback while Vercel/Netlify handles the live domain.1314## When to load1516Load this skill when the user asks to:1718- redesign a foundation / nonprofit / research / education / public-good website19- publish a new landing page, manifesto page, or project site20- ship a privacy-by-default site (consent-first forms, no third-party tracking)21- deploy static HTML/CSS/JS to a VPS mirror at a known domain22- produce a handoff report for an operator to redeploy on Vercel/Netlify2324Do NOT load for: SPA / SSR app deploys (use the framework-specific skill); pure-design mockups without deploy (use `claude-design`); token-spec authoring (use `design-md`).2526## Operating loop (10 steps)27281. **Read the brief completely.** Brand identity, mission, values, strategic themes, palette, typography, constraints. Note any "do not claim X" rules (e.g. "do not claim 501(c)(3) until incorporated").29302. **Survey what already exists on the VPS and in any related repo.** Read the brand file, the current site files, the express/server capture service if any, the nginx config for the domain, and any archives. Build a mental model of the deploy topology BEFORE writing any HTML.31323. **Confirm scope and tone with the user only if truly ambiguous.** If the user has given enough direction (palette, themes, constraints), do NOT ask an A/B/C menu — ship it. Default behavior is to ship. Ask only when the gap materially changes the deliverable.33344. **Design the system, then build the pages.**35 - Palette + type stack + spacing + radii as CSS variables in one `gbf.css`-style file.36 - Logo mark as inline SVG (no raster). Sacred-scientific / geometric / emblem-style works well for foundation sites.37 - 7–9 pages: index, about, themes (or strategic areas), participate, interest form, thanks, faq, privacy, 404.38 - "Forming" / "seeks" / "explores" language until the org is formally established. Avoid: 501(c)(3) claim, named board members, confirmed donors, named partners, confirmed events, tax-deductible status. Use explicit "not tax-deductible" disclaimers where relevant.39405. **Preserve or improve the existing interest-capture flow.** Posts JSON to `/api/interest` (Vercel/serverless). Falls back to `POST /interest` for VPS express capture. Use consent-first (required `consentToContact` checkbox), hidden honeypot for bots, no third-party tracking, no analytics pixels.41426. **Validate locally before deploy.** Run a script that:43 - walks every `href=` and `src=` and confirms internal targets resolve44 - confirms all `var(--xxx)` CSS references are defined45 - confirms every form-field name in the HTML is read by the JS handler46 - audits text for forbidden claims (501(c)(3), named donors, tax-deductible-as-fact, etc.)47 See `references/validation-script.md` for the template.48497. **Deploy to the VPS mirror.** Backup the existing mirror first, then `cp -a` the new tree over it. Preserve any files you don't know about (`find … -mindepth 1 -delete` is wrong if there's something custom).50518. **Smoke-test the live mirror — the gotcha:** nginx serves name-based virtual hosts, so `curl http://127.0.0.1/...` returns the **default** vhost (404), not the mirror's vhost. Always pass `-H "Host: <domain>"`:52 ```53 curl -sS -H "Host: guardianbuilder.org" \54 -o /dev/null -w "%{http_code}\n" http://127.0.0.1/55 ```56 Walk every page and every asset. Then test the backend capture service directly (port from nginx config `proxy_pass` line):57 ```58 curl -sS -X POST http://127.0.0.1:<port>/api/interest \59 -H "Content-Type: application/json" \60 -d '{"name":"...","email":"...","interestAreas":[...],"consentToContact":true}'61 ```62 See `references/deploy-ritual.md` for the full sequence with expected outputs.63649. **Build the deployable package.** A clean `dist/` tree (HTML + css/js/assets, no dev-time README) AND a zipped archive. Use Python's `zipfile` if `zip` CLI is missing.656610. **Write the handoff report to `/root/HermesVault/<Project>-Redesign-YYYY-MM-DD.md`.** Sections: what shipped (file table with bytes), design choices, validation results, deployment status, blockers, next actions for the user. End with a short Telegram-style summary in the chat reply.6768## Status-check discipline (most important pitfall)6970**Never trust a session_log / session_search snippet as evidence of on-disk state.** The session log can be truncated mid-stream; the agent may have shipped far more than the last visible tool call shows. Before answering any "where are we?" or "what's the status?" question:71721. `ls -la <project-dir>` to see actual files and timestamps.732. `wc -c <project-dir>/*.html` to confirm file sizes.743. Cross-check against the session log; flag mismatches openly.7576If the on-disk state is further along than the session log shows, **say so and correct your prior summary**. Don't double-down on the stale read.7778Also: when the user asks "where are we?" mid-stream, **don't ask an A/B menu of next actions**. State the verified status, name what's left, and propose to ship. The user picks "ship it now" by default.7980## Pitfalls8182- **Nginx Host header gotcha** — `curl http://127.0.0.1/` returns the default vhost, not yours. Always `-H "Host: <domain>"` for local smoke tests.83- **Backup the mirror before `find -mindepth 1 -delete`** — there may be files the redesign doesn't know about. `cp -a` first, then rebuild.84- **Form-field drift** — the HTML form names and the JS `data.get(...)` calls must match. Run a consistency check.85- **`var(--xxx)` drift** — defined-but-unused is fine; used-but-undefined is a bug. Scan with a regex.86- **False-claim bleed** — drafting in foundation/education tone invites invented credentials. Audit against a forbidden-phrase list and require explicit "not Y" disclaimers.87- **JS reads multi-value fields with `data.getAll()`, not `data.get()`** — easy to miss in the field-consistency check.88- **`zip` CLI is often missing** on minimal VPS images. Use Python's `zipfile` module.89- **`/api/*` proxy_pass** — nginx forwards to the express service on a local port. The `proxy_pass` line in the nginx site config tells you the port; smoke-test that port directly to verify the backend works independent of nginx.9091## Templates9293- `templates/interest-form.html` — consent-first interest form scaffold (honeypot, areas fieldset, consent checkbox, status region, privacy note). Copy and rename fields.94- `templates/interest-form.js` — submit handler with `/api/interest` primary + `/interest` fallback, honeypot trap, status states.9596## References9798- `references/deploy-ritual.md` — the exact deploy + smoke-test sequence with real shell output examples (Host header, nginx config discovery, backend round-trip).99- `references/validation-script.md` — the Python validator that walks links, vars, and form fields.100- `references/handoff-report-template.md` — the report skeleton used for `/root/HermesVault/<Project>-Redesign-YYYY-MM-DD.md`.101102## Verification before claiming done103104The site is NOT shipped until ALL of the following are true:105- All planned pages exist on disk at the expected sizes.106- Validator reports zero broken links, zero missing assets, zero CSS var drift, zero false claims.107- VPS mirror returns HTTP 200 for every page and every asset (with the correct Host header).108- Backend capture service returns success for a smoke-test POST.109- Handoff report exists at `/root/HermesVault/...`.110- Telegram-style summary in the chat reply.111112If any one of those fails, say so explicitly. Don't paper over with "should work" claims.113114## Final response shape115116Short. Plain text. No markdown headers in the chat reply — this is a CLI terminal.117118Include:119- one-line status ("shipped" / "blocked on X" / "degraded: Y works, Z doesn't")120- the deployable artifact path121- the handoff-report path122- any next action that needs the user