Name the opponent, and stop making phones squint

The transition turns your opponent into a zombie with variant 'c', and the
roster table had no row for it — so the stats screen fell back to the raw key
and the shambler sprite, and the list appeared to hold two shamblers. He is
THE OPPONENT now, and he is not the racket zombie, who keeps coming back.

Type across the menus was sized on a desktop and read tiny on a real phone:
tabs, opponent names, counts and labels all go up a step, and the roster
sprites go from 34x46 to 48x62.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-04 16:39:51 +02:00
1 parent a5a5661534
commit 1fccc11b1d
2 files changed
+35 -31

No files matched your search

+4
View File
@@ -490,6 +490,10 @@ export const ENEMIES = {
h: { name: 'THE BOUNCER', spr: 'z_h1', sub: 'BUILT LIKE A DOOR' }, h: { name: 'THE BOUNCER', spr: 'z_h1', sub: 'BUILT LIKE A DOOR' },
k: { name: 'THE ROLLER MAN', spr: 'z_k1', sub: 'HAULS THE COURT ROLLER' }, k: { name: 'THE ROLLER MAN', spr: 'z_k1', sub: 'HAULS THE COURT ROLLER' },
s: { name: 'THE SPECTATOR', spr: 'z_s1', sub: 'BLOCKS WITH HIS CHAIR' }, s: { name: 'THE SPECTATOR', spr: 'z_s1', sub: 'BLOCKS WITH HIS CHAIR' },
// The man you were playing, after the transition turns him (gameplay.js spawns him as 'c'). One
// per run, and he is NOT the racket zombie below — that one keeps coming back. Missing this row
// is what put a second "SHAMBLER" on the screen: an unmapped key falls back to the shambler art.
c: { name: 'THE OPPONENT', spr: 'z_c1', sub: 'HE TURNED MID-MATCH' },
r: { name: 'THE CLUB PRO', spr: 'z_c1', sub: 'STILL HAS A BACKHAND' }, r: { name: 'THE CLUB PRO', spr: 'z_c1', sub: 'STILL HAS A BACKHAND' },
m: { name: 'THE BALL MACHINE', spr: 'z_mach1', sub: 'FIRES BACK · FURNITURE' }, m: { name: 'THE BALL MACHINE', spr: 'z_mach1', sub: 'FIRES BACK · FURNITURE' },
}; };
+31 -31
View File
@@ -91,7 +91,7 @@
rgba(12,20,17,0.58) 100%), rgba(12,20,17,0.58) 100%),
url('assets/menu_bg.png?v=86') 50% 42% / cover no-repeat var(--ink); url('assets/menu_bg.png?v=86') 50% 42% / cover no-repeat var(--ink);
image-rendering: pixelated; } image-rendering: pixelated; }
#intro .card { font: 400 11px Silkscreen, monospace; color: var(--cream); text-align: center; #intro .card { font: 400 12px Silkscreen, monospace; color: var(--cream); text-align: center;
line-height: 1.9; padding: 16px 14px 20px; width: min(370px, 94vw); line-height: 1.9; padding: 16px 14px 20px; width: min(370px, 94vw);
animation: drop 0.5s ease-out; } animation: drop 0.5s ease-out; }
@keyframes drop { from { transform: translateY(-14px); opacity: 0; } } @keyframes drop { from { transform: translateY(-14px); opacity: 0; } }
@@ -130,8 +130,8 @@
for SET and CHAPTER. A screen title is a thing that shouts. Everything that is a NUMBER or a for SET and CHAPTER. A screen title is a thing that shouts. Everything that is a NUMBER or a
label stays Silkscreen, so the two registers keep meaning what they have always meant. */ label stays Silkscreen, so the two registers keep meaning what they have always meant. */
#intro h2 { margin: 0; font: 400 26px Bangers, cursive; color: var(--acid); letter-spacing: 1.5px; } #intro h2 { margin: 0; font: 400 26px Bangers, cursive; color: var(--acid); letter-spacing: 1.5px; }
#intro h3 { display: flex; justify-content: space-between; margin: 18px 0 8px; #intro h3 { display: flex; justify-content: space-between; margin: 20px 0 9px;
font: 400 9px Silkscreen, monospace; color: var(--dim); letter-spacing: 2px; font: 400 10px Silkscreen, monospace; color: var(--dim); letter-spacing: 2px;
text-align: left; } text-align: left; }
/* header strip: back chevron + title. Every screen but the splash wears one, pinned to the top. */ /* 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; .hdr { position: sticky; top: 0; z-index: 3; display: flex; align-items: center; gap: 10px;
@@ -146,8 +146,8 @@
.figs { display: flex; border: 2px solid var(--line); background: var(--panel); } .figs { display: flex; border: 2px solid var(--line); background: var(--panel); }
.fig { flex: 1; min-width: 0; padding: 7px 6px; text-align: center; } .fig { flex: 1; min-width: 0; padding: 7px 6px; text-align: center; }
.fig + .fig { border-left: 2px solid var(--line); } .fig + .fig { border-left: 2px solid var(--line); }
.fig b { display: block; font: 400 8px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; } .fig b { display: block; font: 400 9px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; }
.fig i { display: block; font: 700 15px Silkscreen, monospace; color: var(--acid); .fig i { display: block; font: 700 18px Silkscreen, monospace; color: var(--acid);
font-style: normal; letter-spacing: 1px; } font-style: normal; letter-spacing: 1px; }
.fig.bad i { color: var(--bad); } .fig.bad i { color: var(--bad); }
/* one button look for every menu screen */ /* one button look for every menu screen */
@@ -156,7 +156,7 @@
straight through the lettering, and the button stops reading as a button. */ straight through the lettering, and the button stops reading as a button. */
.btn { display: block; width: 100%; margin: 0 auto 10px; padding: 12px 10px; .btn { display: block; width: 100%; margin: 0 auto 10px; padding: 12px 10px;
background: var(--solid); border: 2px solid var(--line); color: var(--cream); background: var(--solid); border: 2px solid var(--line); color: var(--cream);
font: 400 12px Silkscreen, monospace; letter-spacing: 1px; cursor: pointer; font: 400 13px Silkscreen, monospace; letter-spacing: 1px; cursor: pointer;
-webkit-tap-highlight-color: transparent; } -webkit-tap-highlight-color: transparent; }
/* The generated button plates are gone with the tutorial. The primary is a flat crimson block /* The generated button plates are gone with the tutorial. The primary is a flat crimson block
now: it survives being resized, which a fixed-size plate crop never did, and the mockup's now: it survives being resized, which a fixed-size plate crop never did, and the mockup's
@@ -165,7 +165,7 @@
font: 400 26px Bangers, cursive; letter-spacing: 2px; line-height: 1.15; } font: 400 26px Bangers, cursive; letter-spacing: 2px; line-height: 1.15; }
.two { display: flex; gap: 8px; } .two { display: flex; gap: 8px; }
.two .btn { margin-bottom: 0; } .two .btn { margin-bottom: 0; }
.btn.sec i { display: block; margin-top: 3px; font: 400 8px Silkscreen, monospace; .btn.sec i { display: block; margin-top: 4px; font: 400 9px Silkscreen, monospace;
font-style: normal; color: var(--dim); letter-spacing: 0.5px; } font-style: normal; color: var(--dim); letter-spacing: 0.5px; }
/* the tour: two tiles SIDE BY SIDE, not two more full-width rows. The figures face the camera — /* the tour: two tiles SIDE BY SIDE, not two more full-width rows. The figures face the camera —
in play you only ever see your player from behind, so a rear view would show nothing. */ in play you only ever see your player from behind, so a rear view would show nothing. */
@@ -175,7 +175,7 @@
color: inherit; font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; } color: inherit; font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
#tours button.on { border-color: var(--acid); background: rgba(188,212,85,0.09); } #tours button.on { border-color: var(--acid); background: rgba(188,212,85,0.09); }
#tours img { height: 88px; width: auto; display: block; image-rendering: pixelated; } #tours img { height: 88px; width: auto; display: block; image-rendering: pixelated; }
#tours b { font: 400 10px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; } #tours b { font: 400 11px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; }
/* a row, not a stacked tile: three 3:2 tiles stacked fill a phone twice over, and the art still /* a row, not a stacked tile: three 3:2 tiles stacked fill a phone twice over, and the art still
reads at this size. -webkit-tap-highlight-color kills iOS's blue flash over the pixel art. */ reads at this size. -webkit-tap-highlight-color kills iOS's blue flash over the pixel art. */
#courtList button.court { display: flex; align-items: center; gap: 11px; width: 100%; #courtList button.court { display: flex; align-items: center; gap: 11px; width: 100%;
@@ -186,13 +186,13 @@
#courtList img { width: 92px; height: 66px; display: block; image-rendering: pixelated; flex: none; #courtList img { width: 92px; height: 66px; display: block; image-rendering: pixelated; flex: none;
object-fit: cover; } object-fit: cover; }
#courtList .cb { flex: 1; min-width: 0; } #courtList .cb { flex: 1; min-width: 0; }
#courtList b { display: block; font: 400 11px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; } #courtList b { display: block; font: 400 12px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; }
#courtList i { display: block; margin: 2px 0 5px; font: 400 8px Silkscreen, monospace; color: var(--dim); font-style: normal; } #courtList i { display: block; margin: 3px 0 6px; font: 400 9px Silkscreen, monospace; color: var(--dim); font-style: normal; }
/* The two meters read straight off the court's own bounce numbers (see main.js), so they cannot /* 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. */ drift away from what the ball actually does on it. */
.meter { display: flex; align-items: center; gap: 6px; margin-top: 3px; .meter { display: flex; align-items: center; gap: 6px; margin-top: 4px;
font: 400 7px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; } font: 400 8px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; }
.meter u { flex: none; width: 40px; text-decoration: none; } .meter u { flex: none; width: 46px; text-decoration: none; }
.meter s { flex: 1; height: 5px; background: rgba(0,0,0,0.45); text-decoration: none; display: block; } .meter s { flex: 1; height: 5px; background: rgba(0,0,0,0.45); text-decoration: none; display: block; }
.meter s > i { display: block; height: 100%; background: var(--acid); } .meter s > i { display: block; height: 100%; background: var(--acid); }
.meter.b s > i { background: #4e8fa6; } .meter.b s > i { background: #4e8fa6; }
@@ -207,49 +207,49 @@
.foot.bare { border: 0; background: none; } .foot.bare { border: 0; background: none; }
.foot.bare .btn.big { margin-bottom: 10px; } .foot.bare .btn.big { margin-bottom: 10px; }
#sSum { display: flex; justify-content: space-between; gap: 8px; margin: 0 0 8px; #sSum { display: flex; justify-content: space-between; gap: 8px; margin: 0 0 8px;
font: 400 8px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; } font: 400 9px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; }
.tabs { display: flex; gap: 6px; margin: 12px 0 14px; } .tabs { display: flex; gap: 6px; margin: 12px 0 14px; }
.tabs button { flex: 1; padding: 9px 2px; background: var(--panel); border: 2px solid var(--line); .tabs button { flex: 1; padding: 11px 2px; background: var(--panel); border: 2px solid var(--line);
color: var(--dim); font: 400 8px Silkscreen, monospace; letter-spacing: 1px; color: var(--dim); font: 400 10px Silkscreen, monospace; letter-spacing: 1px;
cursor: pointer; -webkit-tap-highlight-color: transparent; } cursor: pointer; -webkit-tap-highlight-color: transparent; }
.tabs button.on { border-color: var(--acid); color: var(--acid); } .tabs button.on { border-color: var(--acid); color: var(--acid); }
/* one opponent. The sprite is the walk frame the game already draws, sat on its own baseline. */ /* one opponent. The sprite is the walk frame the game already draws, sat on its own baseline. */
.zrow { display: flex; align-items: center; gap: 10px; padding: 7px 9px; margin-bottom: 6px; .zrow { display: flex; align-items: center; gap: 11px; padding: 9px 10px; margin-bottom: 6px;
border: 2px solid var(--line); background: var(--panel); text-align: left; } border: 2px solid var(--line); background: var(--panel); text-align: left; }
.zrow.boss { border-color: #6e2a24; background: rgba(110,42,36,0.22); } .zrow.boss { border-color: #6e2a24; background: rgba(110,42,36,0.22); }
.zrow img { width: 34px; height: 46px; object-fit: contain; object-position: bottom; .zrow img { width: 48px; height: 62px; object-fit: contain; object-position: bottom;
image-rendering: pixelated; flex: none; } image-rendering: pixelated; flex: none; }
.zb { flex: 1; min-width: 0; display: grid; grid-template-columns: 1fr auto; gap: 0 8px; .zb { flex: 1; min-width: 0; display: grid; grid-template-columns: 1fr auto; gap: 0 8px;
line-height: 1.4; } line-height: 1.4; }
.zb b { font: 400 10px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; .zb b { font: 400 12px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.zb > span { font: 700 12px Silkscreen, monospace; color: var(--acid); text-align: right; } .zb > span { font: 700 15px Silkscreen, monospace; color: var(--acid); text-align: right; }
.zrow.boss .zb > span { color: var(--bad); } .zrow.boss .zb > span { color: var(--bad); }
.pb { grid-column: 1 / -1; height: 5px; margin-top: 4px; background: rgba(0,0,0,0.45); } .pb { grid-column: 1 / -1; height: 6px; margin-top: 4px; background: rgba(0,0,0,0.45); }
.pb s { display: block; height: 100%; background: var(--acid); text-decoration: none; } .pb s { display: block; height: 100%; background: var(--acid); text-decoration: none; }
.zrow.boss .pb s { background: var(--bad); } .zrow.boss .pb s { background: var(--bad); }
.zb i { grid-column: 1 / -1; margin-top: 3px; font: 400 7px Silkscreen, monospace; .zb i { grid-column: 1 / -1; margin-top: 4px; font: 400 9px Silkscreen, monospace;
font-style: normal; color: var(--dim); letter-spacing: 1px; } font-style: normal; color: var(--dim); letter-spacing: 1px; }
#stW { display: flex; flex-wrap: wrap; gap: 6px; } #stW { display: flex; flex-wrap: wrap; gap: 6px; }
.wtile { flex: 1 1 54px; max-width: 76px; padding: 6px 2px; border: 2px solid var(--line); .wtile { flex: 1 1 62px; max-width: 84px; padding: 8px 2px; border: 2px solid var(--line);
background: var(--panel); } background: var(--panel); }
.wtile img { width: 26px; height: 26px; object-fit: contain; display: block; margin: 0 auto 2px; .wtile img { width: 34px; height: 34px; object-fit: contain; display: block; margin: 0 auto 2px;
image-rendering: pixelated; } image-rendering: pixelated; }
.wtile b { display: block; font: 400 9px Silkscreen, monospace; color: var(--acid); } .wtile b { display: block; font: 400 11px Silkscreen, monospace; color: var(--acid); }
.none { margin: 4px 0 0; font: 400 8px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; .none { margin: 4px 0 0; font: 400 9px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px;
text-align: left; } text-align: left; }
#stFoot { display: flex; justify-content: space-between; align-items: baseline; #stFoot { display: flex; justify-content: space-between; align-items: baseline;
border-top: 2px solid var(--line); } border-top: 2px solid var(--line); }
#stFoot b { font: 400 8px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; } #stFoot b { font: 400 9px Silkscreen, monospace; color: var(--dim); letter-spacing: 1px; }
#stFoot i { font: 700 13px Silkscreen, monospace; color: var(--acid); font-style: normal; } #stFoot i { font: 700 16px Silkscreen, monospace; color: var(--acid); font-style: normal; }
.rkrow { display: flex; align-items: center; gap: 10px; padding: 8px 10px; margin-bottom: 5px; .rkrow { display: flex; align-items: center; gap: 10px; padding: 8px 10px; margin-bottom: 5px;
border: 2px solid var(--line); background: var(--panel); text-align: left; } border: 2px solid var(--line); background: var(--panel); text-align: left; }
.rkrow.me { border-color: var(--acid); background: rgba(188,212,85,0.11); } .rkrow.me { border-color: var(--acid); background: rgba(188,212,85,0.11); }
.rkrow b { flex: none; width: 60px; font: 700 10px Silkscreen, monospace; color: var(--dim); } .rkrow b { flex: none; width: 68px; font: 700 12px Silkscreen, monospace; color: var(--dim); }
.rkrow.me b { color: var(--acid); } .rkrow.me b { color: var(--acid); }
.rkrow span { flex: 1; min-width: 0; font: 400 9px Silkscreen, monospace; color: var(--cream); .rkrow span { flex: 1; min-width: 0; font: 400 11px Silkscreen, monospace; color: var(--cream);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rkrow i { font: 400 9px Silkscreen, monospace; font-style: normal; color: var(--dim); } .rkrow i { font: 400 11px Silkscreen, monospace; font-style: normal; color: var(--dim); }
</style> </style>
</head> </head>
<body> <body>