What changed, and why

The Two Ls site is one static page. public/index.html is the whole thing, icons aside; there is no build and there are no tests. Hosting it still took a container: nginx wrapped around public/, built by CI, pushed to ghcr.io, pulled by a k8s pod. This PR retires that whole apparatus. Vercel's git integration serves public/ straight from the repo, so main is production and any PR gets a preview URL for free.

Four files leave. Dockerfile, nginx.conf, .dockerignore, and the whole CI workflow go, while a five-line vercel.json arrives in their place. The infra half of the move, meaning the DNS flip and the k8s stack retirement, is mseeks/zo#21.

One behavior changes on purpose. Today www.2ls.tech serves the page directly. After cutover it will redirect to the apex instead — not automatic platform behavior, but the dashboard's Redirect-to setting, configured when the domains are attached (mseeks/zo#21 carries the step). Apex-as-canonical matches what playeverwhen.com already does, though Vercel's own docs lean the other way (www primary).

vercel.json: nginx's job in five lines

nginx did five things for this site. One of them moves into vercel.json. Three turn out to be platform behavior. The last had no job left:

The whole file: serve public/, clean URLs.

vercel.json · 5 lines
vercel.json5 lines · JSON
1{
2 "$schema": "https://openapi.vercel.sh/vercel.json",
3 "cleanUrls": true,
4 "outputDirectory": "public"
5}
nginx rulewhere it lives now
try_files $uri $uri.html $uri/ (extension-less URLs)cleanUrls: true
Cache-Control: no-cache on the site content (both content locations)Vercel's static default (public, max-age=0, must-revalidate) makes browsers revalidate — same effect
gzip on + typesedge compression (gzip and brotli), no config
.webmanifest served as application/manifest+jsonobserved Vercel behavior (docs are silent; live Vercel sites serve it correctly) — on the cutover checklist
/healthz for the k8s probesdropped — no pod, no probes
Each serving rule, accounted for.

The container path, retired whole

The deleted CI workflow had exactly one job. It built the nginx image and pushed ghcr.io/mseeks/2ls.tech, a :latest and a :<sha>, gated on nothing because the repo has nothing to gate on. Its own comment said as much: "the files under public/ are the artifact." The one consumer of those images was the k8s Deployment pulling :latest, retired in mseeks/zo#21. So the workflow file goes entirely rather than shrinking to an empty jobs: map. A repo with no checks now honestly has no CI.

Dockerfile (14 lines of nginx + COPY public/), the 39-line nginx.conf, and .dockerignore go with it.

The Serving section now tells the Vercel story.

README.md · 34 lines
README.md34 lines · Markdown
⋯ 25 lines hidden (lines 1–25)
1# 2ls.tech
2 
3Company site for **Two Ls LLC** — a one-person indie game & software studio in
4Minneapolis. Only product: [Everwhen](https://playeverwhen.com). Contact:
5hello@2ls.tech.
6 
7One static page, no build step: `public/index.html` is the whole site. Open it
8in a browser and that's 2ls.tech.
9 
10## Design
11 
12The identity is the **"Rewrite" mark**: three plates of the same form — white
13over screen-blended cyan and magenta — slightly out of register, moving on
14quantized clocks, occasionally slicing into bands.
15 
16The page is an original system grown from that physics. The conceit is a press
17proof (**proof nº 2LS-001**): crop marks at the viewport corners, a job slug
18with ink swatches, and a live readout annotating the hero plates' actual
19offsets as they drift. The hero sets the full company name with the mark's two
20lit L's inside "LLC" — the two L's hiding in plain sight. Everwhen's section
21embeds the game's own reel and the **ledger**, a strip of history blocks that
22rewrite themselves one edit at a time (point at one to edit history yourself).
23Every animation runs on `steps()` — nothing eases — and all motion stops under
24`prefers-reduced-motion`.
25 
26## Serving
27 
28Hosted on **Vercel**: a push to `main` deploys production at
29https://2ls.tech (`www` redirects to the apex), and every pull request gets a
30preview URL. There is no build step. [`vercel.json`](./vercel.json) points
31Vercel at `public/` and turns on clean URLs (`/foo` serves `foo.html`), the
32extension-less behavior nginx used to provide; HTML revalidation, compression,
33and TLS are platform defaults. The project and domains are managed in the
34Vercel dashboard.

How to verify it

There is nothing to run pre-merge beyond vercel.json parsing as JSON (it does); the repo has no suite, and this PR does not touch public/.

The real checks come after cutover, against the live hosts. The apex should serve the page with a revalidating cache header. www.2ls.tech should redirect to the apex. /site.webmanifest should come back as application/manifest+json. And /index.html should 308 to /, the cleanUrls fingerprint. The cutover ordering itself (Vercel project and both domains first, DNS second, namespace deletion last) lives in mseeks/zo#21; merging this PR alone changes nothing live.