diff --git a/PROJECT.md b/PROJECT.md index 88cc577..8e89940 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -414,6 +414,46 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu `RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone. +## Session log: THE NETPLAY LOBBY — a link to send, a code to type, a name to be (2026-08-15) + +Oliver: *"multiplayer should be easier to setup — a share with link next to the code; also entering +the code is done in some plain js confirm dialog thingy, this could be embedded more nicely; +somewhere when hosting or also when joining the player should name themselves."* All three are one +screen (`#vs`, `index.html` + THE LOBBY in `main.js`); the protocol underneath is untouched. + +- **THE LOBBY IS A MENU SCREEN, and that is the whole structural change.** Hosting used to call + `start('classic')` first and show the room code in a CANVAS modal over an empty court — which + cannot carry a share button, cannot be typed into, and turned a mistyped code into a CONNECTION + LOST card over a match that never began. The socket is opened from the menu now and the game + stays parked behind the overlay until the room actually fills. **`netHooks.paired` is what takes + the court**, called at the top of `netStart` — one line before everything that writes to it, so + the kit/tour selection `start()` performs is already done when the `look` message reads it. + Two more hooks join `intoSplatter`: `room` (the code landed) and `lost` (returns true when the + lobby said so itself, which is what suppresses the card). Headless leaves them null. +- **SHARE THE LINK, then the code.** `navigator.share` is the phone's own sheet — the friend is in + WhatsApp, not in a room reading letters aloud — with `clipboard.writeText` as the desktop + fallback and the four letters still big on screen for when they are in the same room. The link is + the URL door that already existed (`#net-ABCD`): it opens the lobby with the code prefilled, + ONE tap from the court. Deliberately not an auto-join — that tap is where the joiner sees, and + can change, the name the other phone will call them. +- **ONE NAME PER PLAYER.** The field is prefilled from `stats.savedName()` and written back through + the new `stats.saveName` (which `submit` now shares), so the world board's name and the netplay + name cannot become two things. It crosses on the EXISTING `look` message as `n` — no new message + type, and an older build simply sends none. **The scoreboard names the person** (`render`, the + names block) in both netplay modes: FRIEND until the look lands, never CPU. A stranger's string + lands in an 8-character slot, so it is normalised at the RECEIVING end through the same + `tidyName` the board and `server.py`'s `clean()` agree on. +- **Failure is a typo now, not a card.** A short code never opens a socket; `gone` puts + NO SUCH ROOM — CHECK THE CODE over the picker with the field still there. CANCEL and the header + chevron both `endNetVs`, so a room you walked out of gives its socket back. +- Verified through the real relay in two panes: host → share link → join → both take the court, + host serves, the board reads YOU / GUEST, and the death-match room rebuilds both courts the same + way (blood pips, the friend as the living opponent, no roster body). 96 tests. +- Smaller: the JOIN button is the crimson plate stretched to the field's own height (`#vs .joinrow + .btn` — a bare `.joinrow .btn` loses to `#vs .btn`'s margin, and a margin on a stretched flex + item is exactly the 8px it was coming up short by); `.tin` is now the one typed-field look, worn + by the name prompt and both lobby fields. + ## Session log: gore opens in the gore, the theme comes back, three boards (2026-08-15, same day) Oliver's finetuning pass on the batch below. @@ -678,8 +718,8 @@ agree on the same point from opposite perspectives). 73 tests. serve and rally AI are gated off, `restartRun` closes the socket. - **Honest limits, all deliberate (ponytail)**: NO reconnect-and-resume — a dropped radio is a CONNECTION LOST card and a reload (the recorded plan calls reconnection "the bulk of the real - work"; a prototype must not start there). prompt() is the join UI. The scoreboard still says - CPU. A return snaps the ball to the mirrored strike (~latency of drift eaten in one hop; the + work"; a prototype must not start there). ~~prompt() is the join UI. The scoreboard still says + CPU.~~ *(both gone — see THE NETPLAY LOBBY above.)* A return snaps the ball to the mirrored strike (~latency of drift eaten in one hop; the flight swallows it). Runs bank as classic runs. The wound-decay hook (losing IS zombification) is the recorded next step once the feel survives a two-phone playtest. - **Test on staging with two phones** — the relay deploys with the ordinary push, nothing to diff --git a/index.html b/index.html index f45444b..0020170 100644 --- a/index.html +++ b/index.html @@ -155,7 +155,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { /* ONE overlay, four panels, swapped by #intro[data-stage]. Keeping the backdrop and the card frame alive across every screen is the point — a screen that unmounts the whole overlay flashes the court through between menus. */ - #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #stsub, #bracket { display: none; } + #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #stsub, #bracket, #vs { display: none; } #intro[data-stage="splash"] #splash { display: flex; flex-direction: column; flex: 1; } /* flex COLUMN, not block: it is what lets the footer's margin-top:auto reach the bottom of a screen whose content does not fill it */ @@ -167,6 +167,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { #intro[data-stage="secret"] #secret, #intro[data-stage="bosses"] #bosses, #intro[data-stage="bracket"] #bracket, + #intro[data-stage="vs"] #vs, #intro[data-stage="newbest"] #newbest { display: flex; flex-direction: column; flex: 1; animation: drop 0.3s ease-out; } /* the overlay is HIDDEN during play, not removed: a restart puts it straight back, and the @@ -400,21 +401,53 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { .rkrow span { flex: 1; min-width: 0; font: 400 11px Silkscreen, monospace; color: var(--cream); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .rkrow i { font: 400 11px Silkscreen, monospace; font-style: normal; color: var(--dim); } - /* ---- the name prompt. It is the only screen in the game that asks for typing, so the field is - deliberately big, centred and already focused: on a phone this appears with the keyboard - rising under it and there is no room for a second tap to find the input. */ + /* ---- typed fields. Two screens ask for typing — the name prompt and the netplay lobby — and + both want the same field: big, centred, acid on the panel, no rounded corners anywhere near it. + One class, two size overrides. */ .cent { text-align: center; } + .tin { display: block; width: 100%; box-sizing: border-box; margin: 0 auto; + padding: 12px 10px; background: var(--solid); border: 2px solid var(--line); + border-radius: 0; color: var(--acid); font: 700 16px Silkscreen, monospace; + letter-spacing: 3px; text-align: center; text-transform: uppercase; + -webkit-appearance: none; -webkit-tap-highlight-color: transparent; } + .tin:focus { outline: none; border-color: var(--acid); } + .tin::placeholder { color: var(--dim); letter-spacing: 2px; font-size: 12px; } #newbest h2 { font-size: 30px; margin-bottom: 6px; } #nbScore { margin: 0 0 22px; font: 400 11px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; } #nbScore b { color: var(--acid); font-weight: 400; } - #nbName { display: block; width: 100%; box-sizing: border-box; margin: 0 auto 10px; - padding: 14px 10px; background: var(--solid); border: 2px solid var(--line); - border-radius: 0; color: var(--acid); font: 700 20px Silkscreen, monospace; - letter-spacing: 3px; text-align: center; text-transform: uppercase; - -webkit-appearance: none; -webkit-tap-highlight-color: transparent; } - #nbName:focus { outline: none; border-color: var(--acid); } - #nbName::placeholder { color: var(--dim); letter-spacing: 2px; font-size: 14px; } + #nbName { margin-bottom: 10px; padding: 14px 10px; font-size: 20px; } + #nbName::placeholder { font-size: 14px; } + /* ---- THE NETPLAY LOBBY. Everything the mode needs before there is a court: who you are, the + two ways to host, and the friend's code. The room you are waiting in replaces the picker in + place (#vsWait), so the code and the SHARE button stand where the buttons were. */ + #vsErr:empty { display: none; } + #vsErr { margin: 10px 0 0; padding: 8px; border: 2px solid #6e2a24; background: rgba(110,42,36,0.22); + font: 400 10px Silkscreen, monospace; color: var(--bad); letter-spacing: 1px; + text-align: center; } + #vs .btn { margin-bottom: 8px; } + /* the room takes the whole screen the picker had: the code floats in the middle band and CANCEL + sits under your thumb, same three-band anchoring as every other screen */ + #vsWait { display: flex; flex-direction: column; flex: 1; } + #vsWait[hidden] { display: none; } + .joinrow { display: flex; gap: 8px; align-items: stretch; } + .joinrow .tin { flex: 1; letter-spacing: 8px; } + /* JOIN is the act, not a label beside a box: the crimson plate every primary button in the game + wears, and NO padding of its own — the row stretches it to exactly the field's height, which + its own 13px padding could only ever approximate. */ + /* #vs .btn's margin-bottom outranks a bare .joinrow .btn — and a margin on a stretched flex item + is exactly the 8px the button was coming up short by */ + #vs .joinrow .btn { flex: none; width: 104px; margin: 0; padding: 0 6px; background: var(--red); + border-color: #69201c; color: #f6e7dc; font: 400 22px Bangers, cursive; + letter-spacing: 2px; } + /* the code is the biggest thing on the screen — it is read aloud across a room as often as it is + shared, and a four-letter code you have to squint at is the whole friction being removed */ + #vsBig { margin: 4px 0 20px; font: 700 42px Silkscreen, monospace; color: var(--acid); + letter-spacing: 10px; text-indent: 10px; } + #vsMsg { margin-top: 12px; } + /* the h3 rows are label-left/hint-right everywhere else; this one has no hint and sits over a + centred code, so it centres too (text-align cannot reach it — the row is a flex box) */ + #vsWHdr { justify-content: center; }
@@ -509,6 +542,37 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { + +····
+ +WAITING FOR THEM TO JOIN
+