Files
esh-pfi-infrastructure/stacks/phasefinal-web
vh 85f8a3a072 fix(phasefinal-web): clean up the multi-page site
The 2026-09-28 expansion grew the one-page design to nine pages by
bolting a nav band under each hero. This makes the cluster one site:
- One site header on every page: the wordmark (home link) and a single
  row of short, consistent labels (Applications, Systems, Hosting, AI,
  Engagement, Track record, Contact). The nav no longer wraps into two
  rows at desktop width or four on a phone.
- A real <h1> on every page (the title was a <p>), a skip link, and one
  footer outside <main> on every page, home included.
- The contact page was unreadable: its section borrowed the home page's
  dark contact-card id, so its copy was grey on near-black (2.3:1) and
  its headings measured 1.0:1. It is an ordinary section now.
- Section numbers only where there is a sequence (home, engagement).
- The spec bar names all four services; it stacks cleanly on a phone.
- Contrast: --text-tertiary 4.3:1 -> 4.96:1, --accent-text 4.36:1 ->
  4.74:1 on paper. Track-record labels drop tracked capitals.
- The grid behind the hero and the contact card is its own layer; the
  contact card loses its left side-tab stripe.
- The stylesheet link is versioned (style.css?v=2026-09-28): CSS is
  cached for an hour, and the new markup must not meet the old styles.
- README: nine pages, the shared structure, the CSS-version rule, and the
  operator's anti-slop waivers (the hero's volt rule, Space Grotesk).

Copy is unchanged apart from nav labels (the brief's legal constraints
hold). Still zero JavaScript and zero external requests. Impeccable
detector (controls 7/0, static + rendered at 1280 and 390, light and
dark): 200 unwaived -> 54, all of them the two waived brand devices;
two identical runs. Operator: "ship the site cleanup, keep the rule and
the font".
2026-09-28 16:29:02 -07:00
..

phasefinal-web

The Phase Final, Inc. corporate site — nine static pages served by nginx on ana-docker, fronted by Traefik at www.phasefinal.com.

Built from the design brief at docs/design/ (boothed 2026-08-29); the markup and CSS came from a Claude Design session and are checked in verbatim under conf/site/ apart from the two post-export steps below.

Two steps every fresh export needs

The design tool ships the site with no font binaries and the @font-face block commented out, deliberately, to honour the brief's zero-external- requests rule. Both must be undone on import:

  1. Drop the four woff2 files into conf/site/fonts/ (Space Grotesk variable, IBM Plex Sans variable, IBM Plex Mono 400 + 600 — all SIL OFL, self-hosted, no CDN).
  2. Uncomment the four @font-face rules at the top of conf/site/style.css.

Without these the page silently falls back to system fonts and looks wrong rather than broken, which is the failure mode you won't notice.

Routing — and the healthcheck trap that looks like a routing bug

Routers come from the compose labels: phasefinal-web serves www.phasefinal.com; phasefinal-apex 301s phasefinal.com to it.

⚠ Traefik silently skips containers Docker reports as unhealthy. There is no error, no log line, and no router — it looks exactly like a broken provider. The first deploy of this stack hit that: the healthcheck used http://localhost/, which resolves to ::1 in nginx:alpine, and nginx listens on IPv4 only, so the check got "Connection refused" forever and the container never left unhealthy. Use 127.0.0.1, never localhost, in a healthcheck for an IPv4-only listener — and if a correctly-labelled container never appears in /api/http/routers, check docker inspect --format '{{.State.Health.Status}}' before suspecting Traefik.

Deploy

scripts/deploy-stack.sh ana-docker phasefinal-web
ssh infra-ops@10.250.50.70 'cd /opt/docker/compose/phasefinal-web && docker compose up -d'

DNS

www.phasefinal.com → A 38.120.12.44 (the Anaheim public IP, forwarded to ana-docker's Traefik). The apex phasefinal.com has no record — the site is www-only by operator decision. Cloudflare-proxied is the intended end state, for edge caching and the automatic email-address obfuscation the contact block relies on.

Content constraints

The copy is governed by hard legal and voice constraints — client naming rules, the never-"military"/"weapons" phrasing rule, no named individuals. Read the design brief before editing any text. Edits are made surgically in conf/site/index.html; there is no CMS and there will not be one.

Structure and design rules (cleanup, 2026-09-28)

  • Every page carries the same site header (wordmark + nav), a real <h1>, and the same footer outside <main>. Change the nav in all nine files at once: the labels are Applications, Systems, Hosting, AI, Engagement, Track record, Contact (+ Privacy in the footer).
  • The stylesheet link carries a version (/style.css?v=<date>). CSS is cached for an hour (nginx.conf), so markup that changes with the CSS must bump it, or new pages meet old styles at the edge.
  • Anti-slop waivers, operator ruling 2026-09-28:
    • The hero's full-bleed 3px volt rule is a brand device. The Impeccable detector's side-tab rule flags it; that is waived.
    • Space Grotesk is the brand display face. overused-font is waived.
    • Everything else must stay clean: 0 other unwaived rows at 1280 and 390, light and dark.