The menu text reduction: the design handoff, built

Sixty words on the wall became a dozen. The handoff's own summary of the rule:
Jersey 15 NAMES (screen titles, rooms, cards), Bangers CALLS exactly once per
screen (the fixture, the primary button, a modal headline), Silkscreen INFORMS
(numbers, labels, chips) — and a sentence survives only where it carries a
decision. Everything else moved into a bar, a pip or the modal that opens with
the mode.

  · Jersey 15 bundled subset (9 KB) and Bungee retired after one afternoon
  · SPLASH: the plaque is one row, the goal line is gone (the fixture button
    below said it already), the two doors carry a number each
  · PICK A GAME: rooms are one word, posters are their names, the fixture is a
    hero with the only second line — the whole wall fits one screen now
  · MATCH SETUP: unnumbered blocks, the meters' legend once instead of a
    description per court, no summary footer
  · DIFFICULTY: three pips instead of three sentences
  · TRAINING: the revanche is a full-width row, rungs are numbers, the list
    ends on the league that opens the rest of the ladder
  · VS: three paragraphs became YOU / HOST / JOIN
  · MODALS: one rule per row (the sub was the lead again in other words), the
    blood pool as pips on that same line, and the walk-on's two levels in ONE
    face at ONE size — a comparison drawn at two sizes is a headline with a
    footnote (Oliver)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-31 14:41:25 +02:00
1 parent 4c0ede33db
commit a331a1ae37
44 files changed
+3840 -187

No files matched your search

+116 -64
View File
@@ -47,13 +47,15 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
@font-face { font-family: 'Bangers'; font-weight: 400; font-display: block;
src: url('assets/fonts/Bangers-Regular.ttf?v=86') format('truetype'); }
/* ...and the UPRIGHT display face (2026-08-31). Bangers is drawn slanted — there is no upright
cut of it, which is also why the chevrons are Silkscreen — so the shelf labels that wanted to
shout without leaning needed a third voice. Bungee is an arcade/signage face, upper case by
design, and it stays in-world where a system sans did not. SIL OFL, licence beside it.
SUBSET to the characters this game actually sets (ASCII plus the few marks the copy uses):
10 KB instead of 119 — pyftsubset, and the command is in PROJECT.md if it ever needs redoing. */
@font-face { font-family: 'Bungee'; font-weight: 400; font-display: block;
src: url('assets/fonts/Bungee-Subset.ttf?v=86') format('truetype'); }
cut of it, which is also why the chevrons are Silkscreen — so everything that NAMES rather
than announces needed a third voice. Bungee held it for an afternoon; JERSEY 15 took it in
the menu-text-reduction handoff, because it sits on the same pixel grid as Silkscreen (it
reads as one family with the sprite art) and runs proportionally lighter, where Bungee was
single-weight and blocky. SIL OFL, licence beside it, SUBSET to the characters this game
actually sets — 9 KB; the pyftsubset command is in PROJECT.md. Sizes run ~+40% against the
Bungee values: Jersey sits lower in its box. */
@font-face { font-family: 'Jersey 15'; font-weight: 400; font-display: block;
src: url('assets/fonts/Jersey15-Subset.ttf?v=87') format('truetype'); }
/* html needs a real HEIGHT: the root element's background paints the viewport, but a GRADIENT is
laid out in the root's own box — and with the canvas position:fixed the root has no content and
collapses, so the gradient render.js sets had nowhere to go. */
@@ -121,7 +123,8 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
#quit[hidden], #qAsk[hidden] { display: none; }
.qBox { background: var(--solid); border: 2px solid var(--line); padding: 18px 16px;
width: min(320px, 84vw); display: flex; flex-direction: column; gap: 10px; }
.qBox p { margin: 0 0 6px; font: 400 12px Silkscreen, monospace; color: var(--cream);
/* it is a QUESTION, and a question calls — the one Bangers line on this overlay */
.qBox p { margin: 0 0 10px; font: 400 20px Bangers, cursive; color: var(--cream);
text-align: center; letter-spacing: 1px; }
@media (pointer: coarse) { #hint { display: none; } }
/* ...and it is UI like any other: gone while the splash is still loading (the keyboard hints
@@ -236,14 +239,17 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
/* THREE REGISTERS NOW (2026-08-31). Bangers still ANNOUNCES — the primary button, PROMOTED.,
NEW PERSONAL BEST, and everything the canvas shouts (SET, CHAPTER, EPIC!). Silkscreen is
still the HUD: numbers, labels, anything you read at leisure. And the SCREEN TITLES moved to
Bungee: upright signage, which is what the strip at the top of a screen actually is — you are
not being shouted at, you are being told which room you are in. */
Jersey 15: upright signage, which is what the strip at the top of a screen actually is — you
are not being shouted at, you are being told which room you are in. */
#intro h2 { margin: 0; font: 400 26px Bangers, cursive; color: var(--acid); letter-spacing: 1.5px; }
/* #intro h2 above carries an id, so this needs one too or the announcement face wins */
#intro .hdr h2 { font: 400 23px/1.2 Bungee, Bangers, cursive; letter-spacing: 0.5px; }
#intro h3 { display: flex; justify-content: space-between; margin: 20px 0 9px;
font: 400 10px Silkscreen, monospace; color: var(--dim); letter-spacing: 2px;
text-align: left; }
#intro .hdr h2 { font: 400 33px/1 'Jersey 15', Bangers, cursive; letter-spacing: 1px; }
/* A RUBRIC IS ONE WORD (2026-08-31, the text reduction), in the face that names rooms — and any
hint it carries is a chip on the right, not a second sentence. */
#intro h3 span { font: 400 8px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; }
#intro h3 { display: flex; justify-content: space-between; align-items: baseline; margin: 20px 0 9px;
font: 400 17px/1.2 'Jersey 15', Silkscreen, monospace; color: var(--acid);
letter-spacing: 2px; text-align: left; }
/* header strip: back chevron + title. Every screen but the splash wears one, pinned to the top. */
.hdr { position: sticky; top: 0; z-index: 3; display: flex; align-items: center; gap: 10px;
padding: 10px 12px; margin: 0 -14px; border-bottom: 2px solid var(--line);
@@ -284,8 +290,12 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
font-style: normal; letter-spacing: 0.5px; color: #f0cfc4; }
.two { display: flex; gap: 8px; }
.two .btn { margin-bottom: 0; }
.btn.sec i { display: block; margin-top: 4px; font: 400 9px Silkscreen, monospace;
font-style: normal; color: var(--dim); letter-spacing: 0.5px; }
/* the secondary doors NAME (Jersey) and carry a NUMBER (Silkscreen, acid) — the text reduction
turned "RANKINGS / WORLD BOARD" into "BOARD / BEST 612": a label and a fact, no description */
.btn.sec { font: 400 18px/1.2 'Jersey 15', Silkscreen, monospace; letter-spacing: 1px; }
#qNo { font: 400 17px/1.2 'Jersey 15', Silkscreen, monospace; letter-spacing: 1px; }
.btn.sec i { display: block; margin-top: 3px; font: 700 10px Silkscreen, monospace;
font-style: normal; color: var(--acid); letter-spacing: 1px; }
/* THE PLAYER BLOCK: the live man on the left, everything that changes him on the right, so a tap
and its result are never more than a thumb apart and the whole of "who am I" is one screenful. */
#who, #who2 { display: flex; gap: 12px; align-items: flex-start; }
@@ -396,32 +406,27 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
crest on the left, name over the big LEVEL · LEAGUE line beside it, the chevron on the
right; the bar runs the full width below and the goal line closes left-aligned. Translucent
surface, generous rows — the splash art, blood pool included, stays visible around it. */
#plaque { position: relative; display: grid; column-gap: 14px; align-items: center; text-align: left;
grid-template-columns: auto 1fr;
grid-template-areas: "badge name" "badge lv" "bar bar" "goal goal";
/* ONE ROW (2026-08-31, the text reduction): crest, then name and level on one baseline, the bar
under them with the level it fills toward at its right end. The GOAL LINE is gone — the
fixture button right below says what to do, and saying it twice was the whole complaint. */
#plaque { position: relative; display: flex; align-items: center; gap: 13px; text-align: left;
margin: 24px auto 0; width: min(330px, 92%); box-sizing: border-box;
padding: 18px 34px 15px 18px; background: rgba(8, 14, 10, 0.48); border: 0;
padding: 13px 36px 13px 15px; background: rgba(8, 14, 10, 0.48); border: 0;
border-radius: 12px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
#plBadge { grid-area: badge; height: 54px; image-rendering: pixelated;
#plBadge { flex: none; height: 50px; image-rendering: pixelated;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5)); }
#plName { grid-area: name; align-self: end; padding-bottom: 3px;
font: 700 13px Silkscreen, monospace; color: var(--cream); letter-spacing: 2px;
white-space: nowrap; overflow: hidden; }
#plLv { grid-area: lv; align-self: start; font: 400 26px Bangers, cursive; color: var(--acid);
letter-spacing: 1.5px; text-decoration: none; text-shadow: 2px 2px 0 rgba(0,0,0,0.5); }
/* the bar wears its ENDPOINTS — "20 ▓▓░░ 21" says "fill toward the next level" without a
word of explanation (Oliver read the bare bar as total progress) */
#plRow { grid-area: bar; display: flex; align-items: center; gap: 7px; width: auto;
margin-top: 15px; }
#plCol { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
#plTop { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
#plName { font: 400 21px 'Jersey 15', Bangers, cursive; color: var(--cream); letter-spacing: 0.5px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* the CALL belongs to the button below, not to the plaque — the level is a number now */
#plLv { flex: none; font: 700 10px Silkscreen, monospace; color: var(--acid); letter-spacing: 1px;
text-decoration: none; }
/* the bar wears the level it fills TOWARD at its right end — "▓▓░░ 13" says which way it runs
without a word of explanation (Oliver read a bare bar as total progress) */
#plRow { display: flex; align-items: center; gap: 7px; }
#plRow u, #cdBar u { font: 700 9px Silkscreen, monospace; color: var(--cream);
text-decoration: none; text-shadow: 1px 1px 0 rgba(0,0,0,0.5); }
/* NO nowrap here: the longest goal line is ~380px and a nowrap child sets the CARD's
min-content past the viewport — that was the phantom horizontal scrollbar Oliver spotted */
/* LITERAL gold — there is no --warm in the page palette (UI.warm is a CANVAS colour), and an
unresolvable var() painted this line black */
#plGoal { grid-area: goal; margin-top: 10px; font: 400 9px Silkscreen, monospace;
font-style: normal; color: #e0cc72; letter-spacing: 1px;
text-shadow: 1px 1px 0 rgba(0,0,0,0.5); text-align: left; line-height: 1.6; }
#plaque em { position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
font: 700 15px Silkscreen, monospace; font-style: normal; color: var(--acid); }
#plRow s, #cdBar s, #cdCareer s { flex: 1; height: 6px; background: rgba(255,255,255,0.10);
@@ -458,10 +463,27 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
text-align: center; line-height: 1.35; }
#bossList i, #trainList i { font: 400 8px Silkscreen, monospace; color: var(--dim); font-style: normal;
letter-spacing: 1px; }
/* THE REVANCHE TILE (THE SEASON): the one that put you out of the cup, marked and standing
first. Red border and a red line, because it is the only tile on this screen with a reason. */
#trainList button.put { border-color: var(--red); }
#trainList button.put i { color: #e08a7c; }
/* THE REVANCHE ROW (THE SEASON, widened in the text reduction): the one that put you out of the
cup, across the top of the grid — one body out of eighteen with an actual reason, so it gets
the width to say so in three words. */
#trainList button.put { grid-column: 1 / -1; flex-direction: row; align-items: center; gap: 12px;
padding: 10px 12px; text-align: left; position: relative;
border-color: var(--red); background: rgba(164,35,31,0.16); }
#trainList button.put img { height: 52px; }
#trainList button.put > span { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px;
text-align: left; }
#trainList button.put b { font: 400 24px/1.05 'Jersey 15', Silkscreen, monospace; text-align: left; }
#trainList button.put i { color: #e08a7c; letter-spacing: 1px; }
#trainList button.put em { position: absolute; right: 12px; font: 700 15px Silkscreen, monospace;
font-style: normal; color: var(--bad); }
/* the rung's NAME (Jersey) over its number — "No. 7" was three characters of nothing */
#trainList b { font: 400 17px/1 'Jersey 15', Silkscreen, monospace; letter-spacing: 0.5px; }
/* ...and the list ends on the league that opens the rest of the ladder, drawn as the goal it is */
#trainList .goal { grid-column: 1 / -1; padding: 14px 6px; border: 2px dashed var(--line);
text-align: center; }
#trainList .goal b { display: block; color: #4a5a52; }
#trainList .goal i { font: 400 9px Silkscreen, monospace; font-style: normal; color: #4a5a52;
letter-spacing: 1px; }
/* the training picker is 18 rungs deep — the tile carries the HEAD (the same z_*head crop the
draw and the walk-on wear), which reads at a third of the boss portrait's height */
#trainList img { height: 44px; width: auto; display: block; image-rendering: pixelated; }
@@ -511,8 +533,26 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
as a second card headline and the two competed); Silkscreen, big and letter-spaced, keeps it
a label of a room. No rule, no left band either — the air above each heading does the
grouping, which is what air is for. */
#modeList .mgroup { margin: 0 0 12px; }
#modeList .mgroup + .mgroup { margin-top: 26px; }
#modeList .mgroup { margin: 0 0 10px; }
#modeList .mgroup + .mgroup { margin-top: 17px; }
/* HOW A ROOM DEALS ITS POSTERS (the text reduction). The wall used to be six identical 148px
slabs and ran over two screens; the fixture is a HERO now, the training pair stands side by
side, and the casual three stack shorter. All of it fits one screen. */
#modeList .mgroup .shelf { display: flex; flex-direction: column; }
#modeList .mgroup.pair .shelf { flex-direction: row; gap: 8px; }
#modeList .mgroup.pair button.mode { flex: 1; min-width: 0; min-height: 106px; margin-bottom: 0; }
#modeList .mgroup.stack button.mode { min-height: 96px; }
#modeList .mgroup.hero button.mode { min-height: 142px; }
/* the poster names: Jersey NAMES, and the hero is the one poster that CALLS (Bangers) */
#modeList .cb b { font: 400 30px/1 'Jersey 15', Bangers, cursive; letter-spacing: 0.5px; }
#modeList .mgroup.pair .cb b { font-size: 27px; }
#modeList .mgroup.hero .cb b { font: 400 32px/0.95 Bangers, cursive; letter-spacing: 1px; }
/* ...and the hero's second line is the fixture, the only strap left on the wall */
#modeList .mgroup.hero .cb i { color: #f0cfc4; font-size: 10px; margin-top: 3px; }
/* a fact that changes what a mode IS, not a description of it. Top right like LAST, and UNDER
it on the one poster that could wear both (the chip is appended after LAST in main.banner) */
#modeList .last.chip { top: 7px; }
#modeList .last + .last.chip { top: 30px; }
/* text-align rides down from the card, which centres everything on these screens — the section
header is a header, and headers on this screen are left-aligned (the .hdr title, the posters'
own captions) */
@@ -525,7 +565,8 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
descendant selector here restyled every caption on the wall (it stayed invisible while this
rule still named Silkscreen — the moment the family changed, the posters changed with it). */
#modeList .mgroup > header b { display: block; letter-spacing: 0.5px; color: var(--acid);
text-shadow: none; font: 400 15px/1.3 Bungee, Bangers, cursive; }
text-shadow: none; font: 400 17px/1.2 'Jersey 15', Bangers, cursive;
letter-spacing: 2px; }
/* ...and the strap goes back to the quiet register under it: a shouted line over a system-sans
line was two foreign voices in one header */
#modeList .mgroup > header i { display: block; margin: 4px 0 0; letter-spacing: 0.5px;
@@ -541,18 +582,31 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
#stmode button.mode, #modeSub button.mode { min-height: 148px; }
#modeSub { display: flex; flex-wrap: wrap; gap: 8px; }
#modeSub button.mode { background-position: center; }
#modeSub button.chip { flex: 1 1 100%; padding: 22px 0; background: var(--panel); border: 2px solid var(--line);
color: var(--cream); font: 400 13px Silkscreen, monospace; letter-spacing: 2px;
cursor: pointer; -webkit-tap-highlight-color: transparent; position: relative; }
#modeSub button.chip { flex: 1 1 100%; display: flex; align-items: center; justify-content: space-between;
gap: 10px; padding: 26px 16px; background: var(--panel); border: 2px solid var(--line);
color: var(--cream); cursor: pointer; -webkit-tap-highlight-color: transparent;
position: relative; text-align: left; }
#modeSub button.chip b { font: 400 30px/1 'Jersey 15', Silkscreen, monospace; letter-spacing: 1px; }
/* how hard it is, counted: as many pips lit as the chip is steep, and the last one wears the
colour the game uses for a threat */
.pips { display: flex; gap: 5px; }
.pips i { width: 10px; height: 10px; background: rgba(255,255,255,0.12); }
.pips i.on { background: var(--acid); }
#modeSub button.chip.hot .pips i.on { background: var(--bad); }
#courtList button.court { display: flex; align-items: center; gap: 11px; width: 100%;
margin: 0 0 8px; padding: 7px; background: var(--panel);
border: 2px solid var(--line); color: inherit; font: inherit; text-align: left;
cursor: pointer; -webkit-tap-highlight-color: transparent; }
#courtList button.court.on { border-color: var(--acid); background: rgba(188,212,85,0.09); }
#courtList img { width: 92px; height: 66px; display: block; image-rendering: pixelated; flex: none;
#courtList img { width: 84px; height: 60px; display: block; image-rendering: pixelated; flex: none;
object-fit: cover; }
#courtList .cb, #modeList .cb, #modeSub .cb { flex: 1; min-width: 0; }
#courtList b, #modeList b, #modeSub b { display: block; font: 700 15px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; }
/* the court NAMES itself (Jersey) and its two meters say the rest — the description line is gone */
#courtList b { font: 400 22px/1.1 'Jersey 15', Silkscreen, monospace; letter-spacing: 0.5px; }
#courtList i { margin: 3px 0 5px; font: 700 9px Silkscreen, monospace; color: var(--bad);
letter-spacing: 1px; }
#courtList i[hidden] { display: none; }
#courtList i, #modeList i, #modeSub i { display: block; margin: 4px 0 6px; font: 400 10px Silkscreen, monospace; color: #c9c4b8; font-style: normal; }
/* The two meters read straight off the court's own bounce numbers (see main.js), so they cannot
drift away from what the ball actually does on it. */
@@ -613,7 +667,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
title and the court stay — they are the background and they are never undefined. */
#splash.busy #plaque { visibility: hidden; } /* hidden, but it KEEPS ITS SPACE: display:none
let the title slide down the screen and jump back up the moment the profile landed */
#sSum, #brSum { display: flex; justify-content: space-between; gap: 8px; margin: 0 0 8px;
#brSum { display: flex; justify-content: space-between; gap: 8px; margin: 0 0 8px;
font: 400 9px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; }
.tabs { display: flex; gap: 6px; margin: 12px 0 14px; }
.tabs button { flex: 1; padding: 11px 2px; background: var(--panel); border: 2px solid var(--line);
@@ -738,10 +792,10 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
so the foot's blood pool stays visible. One tap opens the card (refreshSplash fills). -->
<button id="plaque">
<img id="plBadge" alt="">
<b id="plName"></b>
<u id="plLv">LEVEL 1</u>
<span id="plRow"><u id="plFrom">1</u><s><i id="plFill"></i></s><u id="plTo">2</u></span>
<i id="plGoal"></i>
<span id="plCol">
<span id="plTop"><b id="plName"></b><u id="plLv">LEVEL 1</u></span>
<span id="plRow"><s><i id="plFill"></i></s><u id="plTo">2</u></span>
</span>
<em>&gt;</em>
</button>
</div>
@@ -756,10 +810,10 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
already knows the answer to. It names the next cup fixture now and enters the draw;
everything else lives one tap down, behind OTHER GAMES. -->
<button class="btn big" id="mStart"><b id="msTitle">THE CUP</b><i id="msLine"></i></button>
<button class="btn sec" id="mOther">OTHER GAMES<i>CLIMB · TRAINING · GORE · A FRIEND</i></button>
<button class="btn sec" id="mOther">OTHER GAMES</button>
<div class="two">
<button class="btn sec" id="mStats">STATS<i id="mKills">0 KILLS</i></button>
<button class="btn sec" id="mRanks">RANKINGS<i>WORLD BOARD</i></button>
<button class="btn sec" id="mStats">STATS<i id="mKills">0</i></button>
<button class="btn sec" id="mRanks">BOARD<i id="mBest">BEST 0</i></button>
</div>
<p id="build"></p>
</div>
@@ -813,7 +867,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
<h3 id="s02">02 · COURT</h3>
<div id="courtList"></div>
<div class="foot">
<p id="sSum"></p>
<button class="btn big" id="cStart">SERVE UP</button>
</div>
</div>
@@ -893,7 +946,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
Built in main.js from config's roster (matchFoe/LADDER), so a new rung needs no markup. -->
<div id="train">
<div class="hdr"><button class="chev" id="trBack">&lt;</button><h2>TRAINING</h2></div>
<h3>PICK YOUR OPPONENT<span>ONE GAME · GO AGAIN</span></h3>
<div id="trainList"></div>
</div>
<!-- VS A FRIEND. The whole lobby: your name (the world board's name — one name per player),
@@ -903,17 +955,17 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
<div id="vs">
<div class="hdr"><button class="chev" id="vsBack">&lt;</button><h2>VS A FRIEND</h2></div>
<div id="vsPick">
<h3>01 · YOUR NAME<span>ON THEIR SCOREBOARD</span></h3>
<h3>YOU</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>
<h3>HOST</h3>
<button class="btn" id="vsHostC">CLASSIC</button>
<button class="btn" id="vsHostD">DEATH MATCH</button>
<h3>OR JOIN THEIRS<span>WITH THEIR CODE</span></h3>
<h3>JOIN</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>
<button class="btn" id="vsJoin">GO</button>
</div>
<p id="vsErr"></p>
</div>
@@ -999,6 +1051,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
</div>
</div>
</div>
<script type="module" src="src/main.js?v=341"></script>
<script type="module" src="src/main.js?v=342"></script>
</body>
</html>