Files
splatter-tennis/index.html
T
wuwiandClaude Opus 5 8bc57e9358 Tighten the returns, give each chapter its own boss, and let the umpire talk
Five reports from Oliver's second playtest.

The parry was too generous — at 6 m of arm with a 0.34 s window the shot flew
into reach on its own, so any tap once it was vaguely close worked. 3.4 m /
0.2 s / 1.7 m reach now: a tap at 4.5 m is rejected and costs blood, 3.2 m and
in returns. The boss telegraphs in 0.5 s against the machine's 0.9 and throws
harder, because a boss should be worse than furniture.

His three balls were decoration: fanned ±1.9 m across space, so only the middle
one could ever reach you. They are a timed salvo now — three shots 0.42 s apart,
each aimed at where you are at that moment. hurtGrace still caps a whole volley
at one point of damage, which is what it is for.

Every chapter was the same boss. Three designs cycling now, each with its own
sheet, stride, throw pose, wrecked skin and wrecked-throw skin: the
GROUNDSKEEPER, the LINE JUDGE, the CHAIR UMPIRE with his chair strapped on. The
chapter card names them. Designs are data in BOSS_ART, so a fourth is a sheet
plus a row.

Healing announces itself with a first-aid tin and a red cross instead of a 1.4 s
flash. And 14 new umpire lines: ten sarcastic asides on a jittered timer, four
hooked to specific beats. He still has not acknowledged that the match became a
massacre, so he keeps officiating it.

Two traps found and written into the pipeline notes. Sprite loads now carry
?v=SPRV, because python http.server sends no cache headers and the browser
caches 404s — a name added before its PNG exists stays permanently missing for
that browser. And a scripted replace that silently didn't match left drawZombie
on the old hardcoded boss names while everything else moved to BOSS_ART; the
built-in fallback hid it perfectly, all four designs rendering as design 1 with
no error. Assert your rewiring edits matched.

Tennis untouched: 30 tapped points, 50 hits / 0 whiffs. 45-game soak clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 14:56:00 +02:00

66 lines
3.7 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Splatter Tennis</title>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<!-- save-to-homescreen: a bloody tennis ball. iOS needs the apple-* tags (it ignores the
manifest's display/name); Android reads the manifest. -->
<link rel="icon" type="image/png" href="assets/favicon.png">
<link rel="apple-touch-icon" href="assets/icon-180.png">
<link rel="manifest" href="manifest.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="Splatter">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="theme-color" content="#2b2735">
<style>
/* pixel HUD font — bundled so it renders offline; used by the canvas scoreboard + rack */
@font-face { font-family: 'Silkscreen'; font-weight: 400; font-display: block;
src: url('assets/fonts/Silkscreen-Regular.ttf') format('truetype'); }
@font-face { font-family: 'Silkscreen'; font-weight: 700; font-display: block;
src: url('assets/fonts/Silkscreen-Bold.ttf') format('truetype'); }
html, body { margin: 0; height: 100%; background: #10141c; display: grid; place-items: center; }
canvas { image-rendering: pixelated; width: min(96vw, calc(96vh * 16 / 9)); aspect-ratio: 16/9; touch-action: none; }
#hint { position: fixed; bottom: 8px; width: 100%; text-align: center;
font: 400 10px Silkscreen, monospace; color: #6b7688; }
@media (pointer: coarse) { #hint, #intro .kb { display: none; } }
#intro { position: fixed; inset: 0; z-index: 10; touch-action: none; display: grid; place-items: center;
background: radial-gradient(circle at 50% 34%, rgba(34,16,20,0.92), rgba(8,10,16,0.96)); }
#intro .card { font: 400 11px Silkscreen, monospace; color: #dfe6f2; text-align: center; line-height: 1.9;
padding: 18px 26px; max-width: 92vw; animation: drop 0.5s ease-out; }
@keyframes drop { from { transform: translateY(-14px); opacity: 0; } }
/* the logo is generated pixel art now (assets/logo.png), not stacked CSS text */
#intro .logo { display: block; width: min(320px, 78vw); height: auto; margin: 0 auto 8px;
image-rendering: pixelated; filter: drop-shadow(0 3px 0 rgba(0,0,0,0.45)); }
#intro .slogan { margin: 8px 0 16px; font-size: 12px; color: #b6a0a4; letter-spacing: 0.5px; }
#intro .slogan b { color: #c9564a; }
#intro .how { display: inline-block; text-align: left; color: #cbd4e2; line-height: 2; }
#intro .how p { margin: 0; }
#intro .how span { display: inline-block; width: 30px; }
#intro em { color: #8a96ab; font-style: normal; font-size: 12px; }
#intro .start { margin-top: 18px; color: #e0cc72; font-weight: bold; letter-spacing: 1px; animation: blink 1.1s step-end infinite; }
@keyframes blink { 50% { opacity: 0.25; } }
</style>
</head>
<body>
<canvas id="c" width="480" height="270"></canvas>
<div id="hint">move: WASD/arrows &nbsp;·&nbsp; serve/swing: SPACE &nbsp;·&nbsp; aim: hold a direction while swinging &nbsp;·&nbsp; T: tuning panel</div>
<div id="intro">
<div class="card">
<img class="logo" src="assets/logo.png" alt="Splatter Tennis">
<p class="slogan">Fifteen. Thirty. Forty. <b>Dead.</b></p>
<div class="how">
<p><span>👆</span> tap — serve + swing</p>
<p><span>👉</span> flick — aimed shot</p>
<p><span>✋</span> drag — run <em>(auto-run is on)</em></p>
<p class="kb"><span>⌨️</span> WASD / arrows + SPACE</p>
</div>
<p class="start">TAP TO START</p>
</div>
</div>
<script type="module" src="main.js?v=42"></script>
</body>
</html>