Put a menu in front of the game, with Marvin to explain it
The overlay is a four-stage machine now — splash -> menu -> {courts, tutorial}
— swapped by data-stage while the backdrop and card frame stay mounted, so the
live court never flashes through between screens.
Court tiles SELECT only. Tapping one used to start the match on the spot, which
gave you no way to compare the three and no way back out; START GAME and BACK
are their own buttons now. Headlines are 15px against the 11px body, so a screen
reads as a screen rather than as a list.
The tutorial is a data table: a Marvin pose, an optional picture already in
assets/, and his lines. Seven pages covering tennis, the turn, the slingshot,
the rack and the duel. Marvin is four new frames off x_boymop1 — waving,
pointing, shrugging, and clutching his mop in terror.
The theme no longer starts at boot. A title screen that plays at you before you
have touched it is rude, and the autoplay policy would have held it silent until
that first tap anyway.
Death returns to the menu instead of the title. The reload stays — it is the
honest full reset — and a sessionStorage flag is what picks the stage on the way
back in.
Machine-gun rounds are tennis balls, so they are drawn as balls and take the
perspective. A fixed 4x4 fillRect was exactly as big at the far baseline as at
your feet, which read as square confetti pinned to the screen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
60f7ce2cf1
commit
9a65a50cc4
10 files changed
+199
-44
No files matched your search
+62
-12
@@ -76,29 +76,54 @@
|
||||
#build { margin: 10px 0 0; font-size: 9px; color: #5a6270; letter-spacing: 0.5px; }
|
||||
#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; } }
|
||||
/* court select — the intro's second stage. One overlay, two panels, .picking swaps them, so the
|
||||
backdrop and the card frame never flash between screens. */
|
||||
#courts { display: none; }
|
||||
#intro.picking #splash { display: none; }
|
||||
#intro.picking #courts { display: block; animation: drop 0.3s ease-out; }
|
||||
#courts h2 { margin: 0 0 14px; font: 400 11px Silkscreen, monospace; color: #e0cc72; letter-spacing: 2px; }
|
||||
/* 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, #menu, #courts, #tutorial { display: none; }
|
||||
#intro[data-stage="splash"] #splash { display: block; }
|
||||
#intro[data-stage="menu"] #menu { display: block; animation: drop 0.3s ease-out; }
|
||||
#intro[data-stage="courts"] #courts { display: block; animation: drop 0.3s ease-out; }
|
||||
#intro[data-stage="tutorial"] #tutorial { display: block; animation: drop 0.3s ease-out; }
|
||||
/* headline: taller than the body text, so a screen reads as a screen and not as a list */
|
||||
#intro h2 { margin: 0 0 20px; font: 400 15px Silkscreen, monospace; color: #e0cc72;
|
||||
letter-spacing: 3px; line-height: 1.5; }
|
||||
/* one button look for every menu screen */
|
||||
.btn { display: block; width: min(340px, 84vw); margin: 0 auto 10px; padding: 12px 10px;
|
||||
background: rgba(255,255,255,0.04); border: 2px solid #3b4354; color: #dfe6f2;
|
||||
font: 400 12px Silkscreen, monospace; letter-spacing: 1px; cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent; }
|
||||
.btn.go { border-color: #e0cc72; color: #e0cc72; }
|
||||
.btn.back { font-size: 10px; padding: 9px 10px; color: #8a96ab; }
|
||||
/* 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. */
|
||||
#courts button { display: flex; align-items: center; gap: 12px; width: min(340px, 84vw);
|
||||
#courts button.court { display: flex; align-items: center; gap: 12px; width: min(340px, 84vw);
|
||||
margin: 0 auto 8px; padding: 6px; background: rgba(255,255,255,0.04);
|
||||
border: 2px solid #3b4354; color: inherit; font: inherit; text-align: left;
|
||||
cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
||||
#courts button.on { border-color: #e0cc72; background: rgba(224,204,114,0.10); }
|
||||
#courts button.court.on { border-color: #e0cc72; background: rgba(224,204,114,0.10); }
|
||||
#courts img { width: 108px; height: 72px; display: block; image-rendering: pixelated; flex: none; }
|
||||
#courts b { display: block; font: 400 11px Silkscreen, monospace; color: #dfe6f2; letter-spacing: 0.5px; }
|
||||
#courts i { display: block; margin-top: 5px; font: 400 9px Silkscreen, monospace; color: #8a96ab; font-style: normal; }
|
||||
#courts .acts { margin-top: 16px; }
|
||||
/* tutorial: Marvin on the left, his line and the picture on the right */
|
||||
#tutorial .page { display: flex; align-items: flex-end; gap: 14px; width: min(360px, 88vw);
|
||||
margin: 0 auto; text-align: left; min-height: 190px; }
|
||||
#tutorial .marvin { width: 84px; flex: none; image-rendering: pixelated; align-self: flex-end; }
|
||||
#tutorial .say { flex: 1; }
|
||||
#tutorial .say p { margin: 0 0 10px; font: 400 10px Silkscreen, monospace; color: #dfe6f2; line-height: 1.9; }
|
||||
#tutorial .say em { color: #e0cc72; font-style: normal; }
|
||||
#tutorial .shot { display: block; max-width: 100%; max-height: 108px; margin: 0 0 8px;
|
||||
image-rendering: pixelated; }
|
||||
#tutorial .dots { margin: 14px 0 10px; font: 400 10px Silkscreen, monospace; color: #5a6270; letter-spacing: 3px; }
|
||||
#tutorial .acts { display: flex; gap: 8px; width: min(340px, 84vw); margin: 0 auto; }
|
||||
#tutorial .acts .btn { margin: 0; flex: 1; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="480" height="270"></canvas>
|
||||
<div id="safe"></div>
|
||||
<div id="hint">move: WASD/arrows · serve/swing: SPACE · aim: hold a direction while swinging · T: tuning panel</div>
|
||||
<div id="intro">
|
||||
<div id="intro" data-stage="splash">
|
||||
<div class="card">
|
||||
<div id="splash">
|
||||
<img class="logo" src="assets/logo.png" alt="Splatter Tennis">
|
||||
@@ -113,10 +138,35 @@
|
||||
<p class="start">TAP TO START</p>
|
||||
<p id="build"></p>
|
||||
</div>
|
||||
<!-- rows are built from config.COURTS in main.js: adding a court is a table row, never markup -->
|
||||
<div id="courts"><h2>CHOOSE YOUR COURT</h2></div>
|
||||
<div id="menu">
|
||||
<h2>SPLATTER TENNIS</h2>
|
||||
<button class="btn go" id="mStart">START GAME</button>
|
||||
<button class="btn" id="mTut">TUTORIAL</button>
|
||||
</div>
|
||||
<!-- court rows are built from config.COURTS in main.js: adding a court is a table row, never markup -->
|
||||
<div id="courts">
|
||||
<h2>CHOOSE YOUR COURT</h2>
|
||||
<div id="courtList"></div>
|
||||
<div class="acts">
|
||||
<button class="btn go" id="cStart">START GAME</button>
|
||||
<button class="btn back" id="cBack">BACK</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- tutorial pages are built from a table in main.js -->
|
||||
<div id="tutorial">
|
||||
<h2>HOW IT WORKS</h2>
|
||||
<div class="page">
|
||||
<img class="marvin" id="tMarvin" src="assets/marvin1.png" alt="">
|
||||
<div class="say" id="tSay"></div>
|
||||
</div>
|
||||
<p class="dots" id="tDots"></p>
|
||||
<div class="acts">
|
||||
<button class="btn back" id="tBack">BACK</button>
|
||||
<button class="btn go" id="tNext">NEXT</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="main.js?v=90"></script>
|
||||
<script type="module" src="main.js?v=91"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user