feat(phasefinal-web): corporate site stack on ana-docker
Single static page (nginx) fronted by Traefik at www.phasefinal.com, built from the design brief. Site markup/CSS checked in verbatim from the design session; fonts self-hosted (SIL OFL) with the @font-face block uncommented, which every fresh export re-comments. Routed via a Traefik file-provider config rather than the container labels: the docker provider on ana-docker was not registering newly-created containers, so the file router avoids restarting shared ingress. Labels are retained in compose so the file can be dropped once that is fixed.
This commit is contained in:
+1652
-624
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
# phasefinal-web
|
||||
|
||||
The Phase Final, Inc. corporate site — a single static page 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
|
||||
|
||||
`conf/traefik-dynamic-phasefinal-web.yml` deploys to
|
||||
**`/opt/docker/conf/traefik-ana/config/phasefinal-web.yml`** — not into this
|
||||
stack's own conf dir. It exists because Traefik's docker provider on ana-docker
|
||||
was not registering newly-created containers on 2026-08-29; the compose file
|
||||
still carries the correct `traefik.*` labels, so once that is fixed the file
|
||||
router can be deleted. See the header comment in that file.
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
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.
|
||||
@@ -0,0 +1,26 @@
|
||||
services:
|
||||
web:
|
||||
image: nginx:alpine
|
||||
container_name: phasefinal-web
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /opt/docker/conf/phasefinal-web/site:/usr/share/nginx/html:ro
|
||||
- /opt/docker/conf/phasefinal-web/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- tnet
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost/"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.phasefinal-web.rule=Host(`www.phasefinal.com`)
|
||||
- traefik.http.routers.phasefinal-web.tls=true
|
||||
- traefik.http.routers.phasefinal-web.tls.certresolver=anaprod
|
||||
- traefik.http.services.phasefinal-web.loadbalancer.server.port=80
|
||||
|
||||
networks:
|
||||
tnet:
|
||||
name: traefik-net
|
||||
external: true
|
||||
@@ -0,0 +1,18 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Static site; long cache on immutable assets, short on the document so
|
||||
# a dictated edit goes live on the next deploy without a purge.
|
||||
location = / { add_header Cache-Control "public, max-age=300"; try_files /index.html =404; }
|
||||
location ~* \.(woff2|svg|png|ico)$ { add_header Cache-Control "public, max-age=31536000, immutable"; }
|
||||
location ~* \.css$ { add_header Cache-Control "public, max-age=3600"; }
|
||||
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header X-Frame-Options DENY always;
|
||||
add_header Referrer-Policy no-referrer always;
|
||||
|
||||
location / { try_files $uri $uri/ =404; }
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" viewBox="1000 1820 6280 1800" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
|
||||
</defs>
|
||||
<g id="Foreground">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"></metadata>
|
||||
<polygon fill="#ffffff" points="3750,3050 3655,3050 3655,2854 3418,2854 3418,3050 3322,3050 3322,2582 3418,2582 3418,2772 3655,2772 3655,2582 3750,2582 "></polygon>
|
||||
<path fill="#ffffff" d="M4282 3050l-106 0 -43 -112 -205 0 -41 112 -100 0 195 -468 102 0 198 468zm-181 -194l0 0 -71 -186 -70 186 141 0z"></path>
|
||||
<path fill="#ffffff" d="M4732 2911c0,40 -9,72 -26,93 -25,31 -70,46 -136,46l-259 0 0 -82 244 0c25,0 44,-3 56,-10 14,-8 21,-22 21,-43 0,-20 -5,-35 -17,-44 -11,-9 -29,-13 -54,-13l-115 0c-51,0 -89,-10 -114,-31 -27,-22 -40,-57 -40,-104 0,-49 13,-85 39,-108 26,-22 67,-33 122,-33l256 0 0 82 -240 0c-52,0 -78,21 -78,63 0,17 6,30 18,39 11,9 27,13 46,13l129 0c46,0 79,6 99,18 33,19 49,57 49,114z"></path>
|
||||
<path fill="#ffffff" d="M5158 3050l-164 0c-67,0 -119,-17 -156,-52 -40,-38 -60,-96 -60,-173 0,-91 19,-155 58,-193 34,-34 88,-50 161,-50l161 0 0 79 -160 0c-35,0 -62,9 -80,27 -19,19 -29,48 -29,86l269 0 0 79 -271 0c0,44 11,75 31,94 18,16 44,24 80,24l160 0 0 79z"></path>
|
||||
<path fill="#90c73e" d="M5638 2857l-278 0 0 193 -97 0 0 -215c0,-59 2,-101 7,-124 9,-42 28,-73 58,-93 36,-24 90,-36 162,-36l148 0 0 79 -156 0c-43,0 -75,11 -95,33 -18,19 -27,48 -27,84l278 0 0 79z"></path>
|
||||
<polygon fill="#90c73e" points="5806,3050 5706,3050 5706,2582 5806,2582 "></polygon>
|
||||
<polygon fill="#90c73e" points="6285,3050 6174,3050 5973,2694 5973,3050 5878,3050 5878,2582 6001,2582 6193,2920 6193,2582 6285,2582 "></polygon>
|
||||
<path fill="#90c73e" d="M7144 3050l-139 0c-66,0 -113,-16 -141,-47 -25,-29 -38,-77 -38,-142l0 -279 95 0 0 244c0,5 0,10 0,14 0,5 0,9 0,12 0,34 5,58 15,74 16,23 47,35 92,35l116 0 0 89z"></path>
|
||||
<path fill="#ffffff" d="M2656 3296c0,101 -83,183 -184,183l-1295 0 0 -1098c0,-101 82,-205 184,-205l1111 0c101,0 184,82 184,183l0 937zm-201 -542c0,-208 -169,-376 -377,-376 -2,0 -38,0 -38,0l-62 213c52,0 100,0 101,0 90,0 163,73 163,163 0,90 -73,164 -163,164 -1,0 -141,0 -193,0l-62 212c0,0 252,1 254,1 208,0 377,-169 377,-377zm-848 0c0,-90 73,-163 163,-163 0,0 73,0 125,0l62 -213c0,0 -184,0 -187,0 -207,0 -375,168 -376,375l0 0 0 555 212 0 0 -178 133 0 62 -211 -195 0 0 -165 1 0z"></path>
|
||||
<path fill="#ffffff" d="M3236 2618c-31,-24 -82,-36 -155,-36l-212 0 0 82 96 0 0 0 114 0c33,0 57,5 73,15 15,10 23,29 23,55 0,47 -31,70 -91,70l-119 0 0 -95 -96 0 0 341 96 0 0 -164 120 0c2,0 4,1 6,1 4,0 8,0 12,0 43,0 79,-8 108,-23 46,-25 70,-68 70,-130 0,-53 -15,-92 -45,-116z"></path>
|
||||
<polygon fill="#90c73e" points="6616,2582 6515,2582 6320,3050 6420,3050 6563,2670 6634,2856 6531,2856 6501,2938 6665,2938 6709,3050 6814,3050 "></polygon>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" viewBox="1000 1820 6280 1800" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
|
||||
</defs>
|
||||
<g id="Foreground">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"></metadata>
|
||||
<polygon fill="#ffffff" points="3750,3050 3655,3050 3655,2854 3418,2854 3418,3050 3322,3050 3322,2582 3418,2582 3418,2772 3655,2772 3655,2582 3750,2582 "></polygon>
|
||||
<path fill="#ffffff" d="M4282 3050l-106 0 -43 -112 -205 0 -41 112 -100 0 195 -468 102 0 198 468zm-181 -194l0 0 -71 -186 -70 186 141 0z"></path>
|
||||
<path fill="#ffffff" d="M4732 2911c0,40 -9,72 -26,93 -25,31 -70,46 -136,46l-259 0 0 -82 244 0c25,0 44,-3 56,-10 14,-8 21,-22 21,-43 0,-20 -5,-35 -17,-44 -11,-9 -29,-13 -54,-13l-115 0c-51,0 -89,-10 -114,-31 -27,-22 -40,-57 -40,-104 0,-49 13,-85 39,-108 26,-22 67,-33 122,-33l256 0 0 82 -240 0c-52,0 -78,21 -78,63 0,17 6,30 18,39 11,9 27,13 46,13l129 0c46,0 79,6 99,18 33,19 49,57 49,114z"></path>
|
||||
<path fill="#ffffff" d="M5158 3050l-164 0c-67,0 -119,-17 -156,-52 -40,-38 -60,-96 -60,-173 0,-91 19,-155 58,-193 34,-34 88,-50 161,-50l161 0 0 79 -160 0c-35,0 -62,9 -80,27 -19,19 -29,48 -29,86l269 0 0 79 -271 0c0,44 11,75 31,94 18,16 44,24 80,24l160 0 0 79z"></path>
|
||||
<path fill="#ffffff" d="M5638 2857l-278 0 0 193 -97 0 0 -215c0,-59 2,-101 7,-124 9,-42 28,-73 58,-93 36,-24 90,-36 162,-36l148 0 0 79 -156 0c-43,0 -75,11 -95,33 -18,19 -27,48 -27,84l278 0 0 79z"></path>
|
||||
<polygon fill="#ffffff" points="5806,3050 5706,3050 5706,2582 5806,2582 "></polygon>
|
||||
<polygon fill="#ffffff" points="6285,3050 6174,3050 5973,2694 5973,3050 5878,3050 5878,2582 6001,2582 6193,2920 6193,2582 6285,2582 "></polygon>
|
||||
<path fill="#ffffff" d="M7144 3050l-139 0c-66,0 -113,-16 -141,-47 -25,-29 -38,-77 -38,-142l0 -279 95 0 0 244c0,5 0,10 0,14 0,5 0,9 0,12 0,34 5,58 15,74 16,23 47,35 92,35l116 0 0 89z"></path>
|
||||
<path fill="#ffffff" d="M2656 3296c0,101 -83,183 -184,183l-1295 0 0 -1098c0,-101 82,-205 184,-205l1111 0c101,0 184,82 184,183l0 937zm-201 -542c0,-208 -169,-376 -377,-376 -2,0 -38,0 -38,0l-62 213c52,0 100,0 101,0 90,0 163,73 163,163 0,90 -73,164 -163,164 -1,0 -141,0 -193,0l-62 212c0,0 252,1 254,1 208,0 377,-169 377,-377zm-848 0c0,-90 73,-163 163,-163 0,0 73,0 125,0l62 -213c0,0 -184,0 -187,0 -207,0 -375,168 -376,375l0 0 0 555 212 0 0 -178 133 0 62 -211 -195 0 0 -165 1 0z"></path>
|
||||
<path fill="#ffffff" d="M3236 2618c-31,-24 -82,-36 -155,-36l-212 0 0 82 96 0 0 0 114 0c33,0 57,5 73,15 15,10 23,29 23,55 0,47 -31,70 -91,70l-119 0 0 -95 -96 0 0 341 96 0 0 -164 120 0c2,0 4,1 6,1 4,0 8,0 12,0 43,0 79,-8 108,-23 46,-25 70,-68 70,-130 0,-53 -15,-92 -45,-116z"></path>
|
||||
<polygon fill="#ffffff" points="6616,2582 6515,2582 6320,3050 6420,3050 6563,2670 6634,2856 6531,2856 6501,2938 6665,2938 6709,3050 6814,3050 "></polygon>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" viewBox="1000 1820 6280 1800" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
|
||||
</defs>
|
||||
<g id="Foreground">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"></metadata>
|
||||
<polygon fill="#14191f" points="3750,3050 3655,3050 3655,2854 3418,2854 3418,3050 3322,3050 3322,2582 3418,2582 3418,2772 3655,2772 3655,2582 3750,2582 "></polygon>
|
||||
<path fill="#14191f" d="M4282 3050l-106 0 -43 -112 -205 0 -41 112 -100 0 195 -468 102 0 198 468zm-181 -194l0 0 -71 -186 -70 186 141 0z"></path>
|
||||
<path fill="#14191f" d="M4732 2911c0,40 -9,72 -26,93 -25,31 -70,46 -136,46l-259 0 0 -82 244 0c25,0 44,-3 56,-10 14,-8 21,-22 21,-43 0,-20 -5,-35 -17,-44 -11,-9 -29,-13 -54,-13l-115 0c-51,0 -89,-10 -114,-31 -27,-22 -40,-57 -40,-104 0,-49 13,-85 39,-108 26,-22 67,-33 122,-33l256 0 0 82 -240 0c-52,0 -78,21 -78,63 0,17 6,30 18,39 11,9 27,13 46,13l129 0c46,0 79,6 99,18 33,19 49,57 49,114z"></path>
|
||||
<path fill="#14191f" d="M5158 3050l-164 0c-67,0 -119,-17 -156,-52 -40,-38 -60,-96 -60,-173 0,-91 19,-155 58,-193 34,-34 88,-50 161,-50l161 0 0 79 -160 0c-35,0 -62,9 -80,27 -19,19 -29,48 -29,86l269 0 0 79 -271 0c0,44 11,75 31,94 18,16 44,24 80,24l160 0 0 79z"></path>
|
||||
<path fill="#90c73e" d="M5638 2857l-278 0 0 193 -97 0 0 -215c0,-59 2,-101 7,-124 9,-42 28,-73 58,-93 36,-24 90,-36 162,-36l148 0 0 79 -156 0c-43,0 -75,11 -95,33 -18,19 -27,48 -27,84l278 0 0 79z"></path>
|
||||
<polygon fill="#90c73e" points="5806,3050 5706,3050 5706,2582 5806,2582 "></polygon>
|
||||
<polygon fill="#90c73e" points="6285,3050 6174,3050 5973,2694 5973,3050 5878,3050 5878,2582 6001,2582 6193,2920 6193,2582 6285,2582 "></polygon>
|
||||
<path fill="#90c73e" d="M7144 3050l-139 0c-66,0 -113,-16 -141,-47 -25,-29 -38,-77 -38,-142l0 -279 95 0 0 244c0,5 0,10 0,14 0,5 0,9 0,12 0,34 5,58 15,74 16,23 47,35 92,35l116 0 0 89z"></path>
|
||||
<path fill="#14191f" d="M2656 3296c0,101 -83,183 -184,183l-1295 0 0 -1098c0,-101 82,-205 184,-205l1111 0c101,0 184,82 184,183l0 937zm-201 -542c0,-208 -169,-376 -377,-376 -2,0 -38,0 -38,0l-62 213c52,0 100,0 101,0 90,0 163,73 163,163 0,90 -73,164 -163,164 -1,0 -141,0 -193,0l-62 212c0,0 252,1 254,1 208,0 377,-169 377,-377zm-848 0c0,-90 73,-163 163,-163 0,0 73,0 125,0l62 -213c0,0 -184,0 -187,0 -207,0 -375,168 -376,375l0 0 0 555 212 0 0 -178 133 0 62 -211 -195 0 0 -165 1 0z"></path>
|
||||
<path fill="#14191f" d="M3236 2618c-31,-24 -82,-36 -155,-36l-212 0 0 82 96 0 0 0 114 0c33,0 57,5 73,15 15,10 23,29 23,55 0,47 -31,70 -91,70l-119 0 0 -95 -96 0 0 341 96 0 0 -164 120 0c2,0 4,1 6,1 4,0 8,0 12,0 43,0 79,-8 108,-23 46,-25 70,-68 70,-130 0,-53 -15,-92 -45,-116z"></path>
|
||||
<polygon fill="#90c73e" points="6616,2582 6515,2582 6320,3050 6420,3050 6563,2670 6634,2856 6531,2856 6501,2938 6665,2938 6709,3050 6814,3050 "></polygon>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="7" fill="#90C73E"></rect></svg>
|
||||
|
After Width: | Height: | Size: 124 B |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,107 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Phase Final, Inc. — Systems Engineering Consultancy</title>
|
||||
<meta name="description" content="Phase Final, Inc. is a systems engineering consultancy offering systems engineering, infrastructure hosting, and AI services, with a track record in hardware, firmware, and software engineering.">
|
||||
<link rel="icon" href="favicon.svg" type="image/svg+xml">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header id="hero">
|
||||
<div class="hero-inner">
|
||||
<img class="wordmark" src="assets/pfi-wordmark-duotone-dark.svg" alt="Phase Final, Inc.">
|
||||
<div class="hero-copy">
|
||||
<p class="lede">A systems engineering consultancy.</p>
|
||||
<p class="sublede">Phase Final, Inc. has a long track record in hardware, firmware, and software engineering across an unusually wide range of industries: consumer electronics, financial services, health services, government, film and video game production, and industrial automation.</p>
|
||||
</div>
|
||||
<p class="spec-bar">
|
||||
<span>Systems engineering</span>
|
||||
<span>Infrastructure hosting</span>
|
||||
<span>AI services</span>
|
||||
<span class="spec-accent">Fixed-bid or retainer</span>
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<section id="services">
|
||||
<h2><span class="num">01</span>What we do</h2>
|
||||
<div class="section-body">
|
||||
<ul class="service-list">
|
||||
<li>
|
||||
<h3>Systems Engineering</h3>
|
||||
<p>Design and integration of complete systems, hardware and software. Requirements, architecture, implementation, and verification are treated as one problem with one owner.</p>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Infrastructure Hosting</h3>
|
||||
<p>Hosting and operation of client infrastructure. Systems the firm builds can run on infrastructure the firm operates, under the same accountability.</p>
|
||||
</li>
|
||||
<li>
|
||||
<h3>AI Services & Engineering</h3>
|
||||
<p>Engineering and operation of AI systems. Model-backed capability is built, integrated, and run as production software, held to the same standard as everything else the firm ships.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="engagement">
|
||||
<h2><span class="num">02</span>How we engage</h2>
|
||||
<div class="section-body">
|
||||
<p>Engagements are fixed-bid or retainer. Fixed-bid where the scope can be specified: a defined deliverable at a defined price. Retainer where the work is continuous: operation, maintenance, and engineering capacity over time.</p>
|
||||
<p class="referral-note">Most new work arrives by referral.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="track-record">
|
||||
<h2><span class="num">03</span>Where we've done it</h2>
|
||||
<div class="section-body">
|
||||
<dl class="record-list">
|
||||
<div class="record">
|
||||
<dt><span class="idx">A</span> High-end consumer audio and electronics</dt>
|
||||
<dd>Firmware design, software tooling, and R&D engineering for <strong>Audeze</strong> and <strong>Sony</strong>.</dd>
|
||||
</div>
|
||||
<div class="record">
|
||||
<dt><span class="idx">B</span> Portable illumination</dt>
|
||||
<dd>Illumination and lighting technology for <strong>SureFire</strong>, including flashlights. The firm holds patents in lighting technology.</dd>
|
||||
</div>
|
||||
<div class="record">
|
||||
<dt><span class="idx">C</span> Financial services</dt>
|
||||
<dd>Mortgage lending underwriting engines for <strong>Fannie Mae</strong> and <strong>Freddie Mac</strong>. Credit card processing and gift card systems.</dd>
|
||||
</div>
|
||||
<div class="record">
|
||||
<dt><span class="idx">D</span> Health services and medical industry</dt>
|
||||
<dd>Document automation and billing systems.</dd>
|
||||
</div>
|
||||
<div class="record">
|
||||
<dt><span class="idx">E</span> Government and DoD</dt>
|
||||
<dd>Hardware and firmware, as a subcontractor to prime contractors.</dd>
|
||||
</div>
|
||||
<div class="record">
|
||||
<dt><span class="idx">F</span> Film and video game production</dt>
|
||||
<dd>Automated asset processing, rapid prototyping, and workflow automation.</dd>
|
||||
</div>
|
||||
<div class="record">
|
||||
<dt><span class="idx">G</span> Industrial automation</dt>
|
||||
<dd>CNC systems automation, additive manufacturing, and robotics automation.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="contact">
|
||||
<h2><span class="num">04</span>Contact</h2>
|
||||
<p class="contact-address"><a href="mailto:inquiry@phasefinal.com">inquiry@phasefinal.com</a></p>
|
||||
<p class="contact-note">Tell us the spec and the deadline. We'll tell you what it takes to reach the final phase.</p>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<span>Phase Final, Inc.</span>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,255 @@
|
||||
/* Phase Final, Inc. — single-page site
|
||||
Brand: Volt #90C73E over cool slate neutrals, per Phase Final tokens.
|
||||
Zero external requests; no JavaScript.
|
||||
|
||||
FONTS: the brand faces are Space Grotesk (display), IBM Plex Sans (body),
|
||||
IBM Plex Mono (labels). To keep this zip free of external requests they are
|
||||
referenced by local name only, with system fallbacks. To ship the exact
|
||||
faces, place woff2 files in fonts/ and uncomment the @font-face blocks. */
|
||||
|
||||
@font-face { font-family: "Space Grotesk"; src: url("fonts/SpaceGrotesk-Var.woff2") format("woff2"); font-weight: 300 700; font-display: swap; }
|
||||
@font-face { font-family: "IBM Plex Sans"; src: url("fonts/IBMPlexSans-Var.woff2") format("woff2"); font-weight: 100 700; font-display: swap; }
|
||||
@font-face { font-family: "IBM Plex Mono"; src: url("fonts/IBMPlexMono-Regular.woff2") format("woff2"); font-weight: 400; font-display: swap; }
|
||||
@font-face { font-family: "IBM Plex Mono"; src: url("fonts/IBMPlexMono-SemiBold.woff2") format("woff2"); font-weight: 600; font-display: swap; }
|
||||
|
||||
:root {
|
||||
--ink: #0f1318;
|
||||
--ink-2: #181d22;
|
||||
--text: #0f1318;
|
||||
--text-secondary: #4d5763;
|
||||
--text-tertiary: #6b7682;
|
||||
--paper: #f6f8f9;
|
||||
--border: #c2cad2;
|
||||
--border-subtle: #dde2e7;
|
||||
--accent: #90c73e;
|
||||
--accent-text: #588021; /* AA on light */
|
||||
--volt-200: #d0e8a0;
|
||||
--volt-300: #b8db72;
|
||||
--volt-400: #a3d052;
|
||||
--volt-500: #90c73e;
|
||||
--slate-300: #c2cad2;
|
||||
--slate-400: #97a2ad;
|
||||
--slate-700: #394149;
|
||||
--slate-800: #262c33;
|
||||
--display: "Space Grotesk", "IBM Plex Sans", -apple-system, "Segoe UI", Helvetica, sans-serif;
|
||||
--sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||
--mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text: #f6f8f9;
|
||||
--text-secondary: #c2cad2;
|
||||
--text-tertiary: #97a2ad;
|
||||
--paper: #0f1318;
|
||||
--border: #394149;
|
||||
--border-subtle: #262c33;
|
||||
--accent-text: #b8db72;
|
||||
--ink: #0a0d11;
|
||||
--ink-2: #181d22;
|
||||
}
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--paper);
|
||||
color: var(--text);
|
||||
font-family: var(--sans);
|
||||
font-size: 16.5px;
|
||||
line-height: 1.65;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
::selection { background: var(--volt-200); color: #0f1318; }
|
||||
|
||||
a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
|
||||
p { margin: 0; text-wrap: pretty; }
|
||||
|
||||
/* ---- Hero: ink console surface with engineering grid ---- */
|
||||
#hero {
|
||||
background: var(--ink);
|
||||
color: #f6f8f9;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-image:
|
||||
linear-gradient(rgba(246,248,249,0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(246,248,249,0.05) 1px, transparent 1px);
|
||||
background-size: 48px 48px;
|
||||
border-bottom: 3px solid var(--volt-500);
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
max-width: 920px;
|
||||
margin: 0 auto;
|
||||
padding: clamp(56px, 9vw, 104px) 28px clamp(56px, 8vw, 96px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: clamp(40px, 6vw, 64px);
|
||||
}
|
||||
|
||||
.wordmark { height: clamp(48px, 7.5vw, 72px); width: auto; display: block; align-self: flex-start; }
|
||||
|
||||
.hero-copy { max-width: 800px; }
|
||||
|
||||
.lede {
|
||||
font-family: var(--display);
|
||||
font-size: clamp(32px, 5.6vw, 54px);
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
text-wrap: balance;
|
||||
}
|
||||
.sublede {
|
||||
margin-top: 24px;
|
||||
font-size: clamp(16.5px, 2vw, 19px);
|
||||
color: var(--slate-300);
|
||||
max-width: 62ch;
|
||||
}
|
||||
.intro-note {
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
color: var(--slate-400);
|
||||
max-width: 62ch;
|
||||
}
|
||||
|
||||
.spec-bar {
|
||||
font-family: var(--mono);
|
||||
font-size: 12.5px;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
color: var(--slate-400);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 0;
|
||||
}
|
||||
.spec-bar span { padding: 0 18px; border-left: 1px solid var(--slate-700); }
|
||||
.spec-bar span:first-child { padding-left: 0; border-left: none; }
|
||||
.spec-bar .spec-accent { color: var(--volt-300); }
|
||||
|
||||
/* ---- Body ---- */
|
||||
main {
|
||||
max-width: 920px;
|
||||
margin: 0 auto;
|
||||
padding: 0 28px 72px;
|
||||
}
|
||||
|
||||
section {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 52px 0 64px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 32px;
|
||||
}
|
||||
#services { border-top: none; padding-top: clamp(52px, 7vw, 76px); }
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
flex: 0 0 208px;
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
h2 .num { color: var(--accent-text); margin-right: 12px; }
|
||||
|
||||
.section-body { flex: 1 1 380px; max-width: 620px; }
|
||||
|
||||
/* Services */
|
||||
.service-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
.service-list h3 {
|
||||
margin: 0 0 6px;
|
||||
font-family: var(--display);
|
||||
font-size: 22px;
|
||||
line-height: 1.3;
|
||||
letter-spacing: -0.01em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.service-list p { color: var(--text-secondary); }
|
||||
|
||||
/* Engagement */
|
||||
#engagement .section-body p + p { margin-top: 18px; }
|
||||
.referral-note { color: var(--text-secondary); }
|
||||
|
||||
/* Track record */
|
||||
.record-list { margin: 0; }
|
||||
.record { padding: 24px 0 26px; }
|
||||
.record:first-child { padding-top: 0; }
|
||||
.record + .record { border-top: 1px solid var(--border-subtle); }
|
||||
.record dt {
|
||||
font-family: var(--mono);
|
||||
font-size: 12.5px;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.record .idx { color: var(--accent-text); margin-right: 6px; }
|
||||
.record dd { margin: 0; font-size: 17.5px; }
|
||||
.record strong { font-family: var(--display); font-weight: 600; }
|
||||
|
||||
/* Contact — ink console panel */
|
||||
#contact {
|
||||
display: block;
|
||||
border-top: none;
|
||||
background: var(--ink-2);
|
||||
background-image:
|
||||
linear-gradient(rgba(246,248,249,0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(246,248,249,0.05) 1px, transparent 1px);
|
||||
background-size: 48px 48px;
|
||||
border-left: 3px solid var(--volt-500);
|
||||
border-radius: 16px;
|
||||
padding: clamp(40px, 6vw, 72px);
|
||||
color: #f6f8f9;
|
||||
}
|
||||
#contact h2 { color: var(--slate-400); margin-bottom: 20px; }
|
||||
#contact h2 .num { color: var(--volt-400); }
|
||||
.contact-address {
|
||||
font-size: clamp(23px, 4vw, 38px);
|
||||
line-height: 1.25;
|
||||
}
|
||||
.contact-address a {
|
||||
font-family: var(--display);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.015em;
|
||||
color: var(--volt-300);
|
||||
text-decoration: none;
|
||||
border-bottom: 2px solid var(--volt-500);
|
||||
}
|
||||
.contact-address a:hover { color: var(--volt-200); }
|
||||
.contact-note {
|
||||
margin-top: 20px;
|
||||
color: var(--slate-300);
|
||||
max-width: 56ch;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
padding-top: 28px;
|
||||
font-family: var(--mono);
|
||||
font-size: 12.5px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
main { padding: 0 20px 56px; }
|
||||
.hero-inner { padding-left: 20px; padding-right: 20px; }
|
||||
section { padding: 40px 0 48px; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Traefik file-provider router for phasefinal-web.
|
||||
#
|
||||
# DEPLOYS TO: ana-docker:/opt/docker/conf/traefik-ana/config/phasefinal-web.yml
|
||||
# (NOT /opt/docker/conf/phasefinal-web/ — the file provider watches
|
||||
# traefik-ana/config, so this file lives with Traefik, not the stack.)
|
||||
#
|
||||
# WHY A FILE ROUTER instead of container labels: as of 2026-08-29 the Traefik
|
||||
# docker provider on ana-docker was not registering newly-created containers —
|
||||
# phasefinal-web carried correct traefik.* labels, sat on traefik-net, and never
|
||||
# appeared in /api/http/routers, with no provider activity in the logs. The file
|
||||
# provider (watch: true) picks this up with no Traefik restart, so no other
|
||||
# public service is interrupted. If the docker provider is fixed, this file can
|
||||
# be deleted and the compose labels will take over.
|
||||
http:
|
||||
routers:
|
||||
phasefinal-web:
|
||||
rule: "Host(`www.phasefinal.com`)"
|
||||
service: phasefinal-web
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
certResolver: anaprod
|
||||
services:
|
||||
phasefinal-web:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://phasefinal-web:80"
|
||||
Reference in New Issue
Block a user