A room you can share by link, a code you can type, and a name to be called

Multiplayer setup moves out of a canvas modal and a prompt() dialog into one
menu screen: your name, the two ways to host, and the friend's code. The socket
opens from the menu and the game stays parked behind the overlay until the room
fills — netHooks.paired takes the court one line before netStart writes to it —
so the room code can carry a real SHARE button and a wrong code is a typo you
fix rather than a CONNECTION LOST card over a match that never began.

The name is the world board's name (stats.saveName), crosses on the existing
look message, and the scoreboard finally names the person instead of CPU.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-15 16:20:39 +02:00
1 parent ad258ace97
commit 425c18dcb6
7 files changed
+255 -75

No files matched your search

+76 -12
View File
@@ -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; }
</style>
</head>
<body>
@@ -509,6 +542,37 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
<button class="btn big" id="cupGo" disabled>ENTER THE DRAW</button>
</div>
</div>
<!-- VS A FRIEND. The whole lobby: your name (the world board's name — one name per player),
the host's one decision, and the friend's code. #vsWait replaces the picker in place once
a socket is open, so the room code and its SHARE button stand where the buttons were and
the game stays parked behind this overlay until the room fills (main.js, THE LOBBY). -->
<div id="vs">
<div class="hdr"><button class="chev" id="vsBack">&lsaquo;</button><h2>VS A FRIEND</h2></div>
<div id="vsPick">
<h3>01 · YOUR NAME<span>ON THEIR SCOREBOARD</span></h3>
<input id="vsName" class="tin" maxlength="12" placeholder="YOUR NAME" autocomplete="off"
autocapitalize="characters" spellcheck="false">
<h3>02 · HOST A MATCH<span>THEY COME TO YOU</span></h3>
<button class="btn" id="vsHostC">CLASSIC TENNIS</button>
<button class="btn" id="vsHostD">DEATH MATCH</button>
<h3>OR JOIN THEIRS<span>WITH THEIR CODE</span></h3>
<div class="joinrow">
<input id="vsCode" class="tin" maxlength="4" placeholder="CODE" autocomplete="off"
autocapitalize="characters" spellcheck="false">
<button class="btn" id="vsJoin">JOIN</button>
</div>
<p id="vsErr"></p>
</div>
<div id="vsWait" hidden>
<div class="mid cent">
<h3 id="vsWHdr" class="cent">ROOM CODE</h3>
<p id="vsBig">····</p>
<button class="btn big" id="vsShare">SHARE THE LINK</button>
<p class="none cent" id="vsMsg">WAITING FOR THEM TO JOIN</p>
</div>
<div class="foot bare"><button class="btn" id="vsCancel">CANCEL</button></div>
</div>
</div>
<!-- BODY COUNT. Every row in here is built by stats.js from the run records in localStorage. -->
<div id="stats">
<div class="hdr"><button class="chev" id="stBack">&lsaquo;</button><h2 id="stTitle">BODY COUNT</h2></div>
@@ -581,6 +645,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
</div>
</div>
</div>
<script type="module" src="src/main.js?v=265"></script>
<script type="module" src="src/main.js?v=266"></script>
</body>
</html>