diff --git a/README.md b/README.md index 82eb468..2c10dda 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,29 @@ WebSocket server to it. Matchmaking and room/session logic are not implemented yet — see `backend/src/matchmaking/README.md` and `backend/src/rooms/README.md`. +## Deployment + +The frontend (static Vite build) and backend (persistent Node +HTTP+WebSocket process) deploy independently and to different kinds of +host. + +**Frontend on Netlify:** the root `netlify.toml` builds from +`frontend/` (`npm run build`, publish `frontend/dist`). Point this site +at your deployed backend by setting `VITE_WS_URL` under Site settings +-> Environment variables (or `netlify env:set VITE_WS_URL +wss://your-backend-host`) — it's read at build time by +`frontend/src/net/client.ts`. Use `wss://`, not `ws://`: once the +Netlify site is served over https, a plain `ws://` socket is blocked as +mixed content (the client warns in the console if this is +misconfigured). See `frontend/.env.example`. + +**Backend:** Netlify doesn't run long-lived Node processes, so +`backend/` needs a real host (Fly.io, Render, Railway, a VPS, ...) that +can keep a WebSocket server alive. It listens on `process.env.PORT` +(see `backend/src/index.js`), which most such hosts set automatically; +override it manually if yours doesn't. Whatever host/port it ends up +on, put that address into the frontend's `VITE_WS_URL` above. + ## Status Single-player game: playable. Multiplayer: backend is a bare HTTP+WS diff --git a/frontend/.env.example b/frontend/.env.example index 2ec742f..646bc57 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1 +1,13 @@ +# Multiplayer WebSocket backend URL, read at build time via +# `import.meta.env.VITE_WS_URL` (see src/net/client.ts). Defaults to +# ws://localhost:8080 for local dev when unset. +# +# For a Netlify deploy, set this in Site settings -> Environment +# variables (or `netlify env:set VITE_WS_URL wss://your-backend-host`) +# to the deployed backend's address. Use wss:// (not ws://) once the +# Netlify site is served over https, or the browser blocks the +# connection as mixed content. The backend's own listen port is +# separately configurable via its host's PORT env var — see +# backend/src/index.js — and is not set here; it only affects which +# port the backend process binds to, not this URL. VITE_WS_URL=ws://localhost:8080 diff --git a/frontend/src/net/client.ts b/frontend/src/net/client.ts index ea1ebdb..cc49689 100644 --- a/frontend/src/net/client.ts +++ b/frontend/src/net/client.ts @@ -10,6 +10,17 @@ const DEFAULT_WS_URL = 'ws://localhost:8080'; * dev backend. */ export const WS_URL = import.meta.env.VITE_WS_URL || DEFAULT_WS_URL; +// A page served over https can't open a plain ws:// socket (browsers +// block it as mixed content) — this only bites once VITE_WS_URL is +// left unset/misconfigured on an https deploy (e.g. Netlify), so warn +// loudly instead of failing silently as a closed connection. +if (typeof location !== 'undefined' && location.protocol === 'https:' && WS_URL.startsWith('ws://')) { + console.warn( + `[net] VITE_WS_URL is "${WS_URL}" but this page is served over https — ` + + 'the browser will block it as mixed content. Set VITE_WS_URL to a wss:// URL.', + ); +} + /** Lifecycle of the underlying socket. `closed` covers both a clean * close and an error — reconnecting is out of scope here, callers that * care just need to know the connection is gone. */ diff --git a/frontend/src/style.css b/frontend/src/style.css index 94fbeee..c08b744 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -257,12 +257,12 @@ body { } .panel-col { - width: 330px; - flex: 0 0 330px; + width: 260px; + flex: 0 0 260px; min-height: 0; display: flex; flex-direction: column; - gap: 8px; + gap: 6px; } .panel { @@ -271,7 +271,7 @@ body { overflow-y: auto; display: grid; grid-template-columns: 1fr 1fr; - gap: 8px; + gap: 6px; align-content: start; padding: 2px 6px 2px 2px; } @@ -280,9 +280,9 @@ body { flex: 0 0 auto; display: flex; flex-direction: column; - gap: 8px; + gap: 6px; } -.panel-actions .btn { width: 100%; } +.panel-actions .btn { width: 100%; padding: 7px 10px; font-size: 9px; } /* `game/multiplayerMatch.ts`'s mutual reveal-vote status line, shown * below the Reveal button once the local player has voted to reveal * early and is waiting on the opponent to do the same (or vice versa). */ @@ -361,10 +361,10 @@ body { .cat-card { background: var(--panel-2); border: 3px solid var(--ink); - padding: 8px; + padding: 5px 6px; display: flex; align-items: center; - gap: 8px; + gap: 6px; cursor: pointer; box-shadow: 3px 3px 0 var(--ink); transition: transform .08s ease, box-shadow .08s ease; @@ -386,7 +386,7 @@ body { } .cat-swatch { - width: 24px; height: 24px; + width: 16px; height: 16px; border: 2px solid var(--ink); flex-shrink: 0; background: repeating-conic-gradient(#4b4540 0% 25%, #302f2a 0% 50%) 0 0/8px 8px; @@ -401,15 +401,15 @@ body { .cat-info { min-width: 0; } .cat-label { - font-size: 8px; - line-height: 1.5; + font-size: 7px; + line-height: 1.4; font-family: var(--pixel); color: var(--fg); white-space: normal; display: block; - margin-bottom: 4px; + margin-bottom: 2px; } -.cat-count { font-size: 10px; color: var(--fg-dim); } +.cat-count { font-size: 9px; color: var(--fg-dim); } /* ---------- popover ---------- */ diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..267fedd --- /dev/null +++ b/netlify.toml @@ -0,0 +1,16 @@ +[build] +base = "frontend" +command = "npm run build" +publish = "dist" + +[build.environment] +NODE_VERSION = "20" + +# The multiplayer WebSocket backend (backend/) is a persistent Node +# process — it does not run on Netlify. Deploy it separately (Fly.io, +# Render, Railway, a VPS, ...) and point this site at it by setting +# VITE_WS_URL in Site settings -> Environment variables (Netlify +# dashboard) or via `netlify env:set VITE_WS_URL wss://your-backend-host`. +# It must be `wss://` (not `ws://`) once this site is served over +# https, or browsers block the connection as mixed content. See +# frontend/.env.example and frontend/src/net/client.ts.