Draw the tutorial properly, and put the logo and gore on the menu
The tutorial was borrowing a court tile, the in-game how-to icon and a weapon icon, none of which were drawn to be looked at. Four purpose-made 2:1 panels now: the serve with a tapping hand, the slingshot pull with chevrons into a zombie, the weapon rack, and the floodlit duel. They get the full card width above Marvin instead of being squeezed into the text column beside him. The menu wears the title logo instead of a Silkscreen headline, and the two buttons wear generated plates — START GAME bloodied, TUTORIAL nearly clean. The lettering stays Silkscreen on top: generated type is a lottery and would not match the rest of the HUD. Two things the art needed. Quantise the panels at 128 colours, not 64 — big flat fields with saturated accents is where a small palette shows, and 64 turned the tennis ball beige. And find a plate by its BRIGHT BORDER, never by the corner colour: one plate came back on a near-black margin, the other on a transparency checkerboard that differs from itself everywhere and matched the whole canvas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9a65a50cc4
commit
4a034773aa
9 files changed
+46
-13
No files matched your search
+18
@@ -460,6 +460,24 @@ screen that unmounts the overlay flashes the live court through between menus. C
|
|||||||
only; `START GAME` and `BACK` are their own buttons. The tutorial is a data table (`TUT` in main.js)
|
only; `START GAME` and `BACK` are their own buttons. The tutorial is a data table (`TUT` in main.js)
|
||||||
of Marvin poses, an optional picture already in `assets/`, and his lines — adding a page is a row.
|
of Marvin poses, an optional picture already in `assets/`, and his lines — adding a page is a row.
|
||||||
|
|
||||||
|
**Menu and tutorial art is generated DIRECTLY, not through `_regen.py`.** That pipeline keys magenta
|
||||||
|
and slices figures out of a sheet; these are whole illustrated panels with backgrounds, so they go
|
||||||
|
through `generate_image.py` and a downscale/quantise pass, the same route the court tiles took. Two
|
||||||
|
things learned making them:
|
||||||
|
|
||||||
|
- **Quantise these at 128 colours, not 64.** The panels are big flat fields with a few saturated
|
||||||
|
accents (the ball, fire, blood), which is exactly where a small palette shows — 64 turned the
|
||||||
|
tennis ball beige.
|
||||||
|
- **Find a generated plate by its BRIGHT BORDER, never by the corner colour.** One button plate came
|
||||||
|
back on a near-black margin and the other on a transparency CHECKERBOARD, which differs from
|
||||||
|
itself everywhere and matched the entire canvas. The threshold has to adapt per image too: the
|
||||||
|
checkerboard's own greys are brighter than the darker plate's border.
|
||||||
|
|
||||||
|
The button plates are blank on purpose — **the lettering stays Silkscreen on top of the art**.
|
||||||
|
Generated type is a lottery and would not match the rest of the HUD. `btn_start` is a BOTTOM band of
|
||||||
|
a taller plate: by there the drips have run out to the ends and the centre is clear enough to print
|
||||||
|
over, and the plate's own bottom border comes with it.
|
||||||
|
|
||||||
**The menu theme starts on the FIRST TAP, never at boot** (`go()` calls `menuMusic`). A title screen
|
**The menu theme starts on the FIRST TAP, never at boot** (`go()` calls `menuMusic`). A title screen
|
||||||
that plays at you before you touch it is rude, and on a phone the autoplay policy would have held it
|
that plays at you before you touch it is rude, and on a phone the autoplay policy would have held it
|
||||||
silent until that tap regardless.
|
silent until that tap regardless.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
+19
-6
@@ -93,6 +93,15 @@
|
|||||||
font: 400 12px Silkscreen, monospace; letter-spacing: 1px; cursor: pointer;
|
font: 400 12px Silkscreen, monospace; letter-spacing: 1px; cursor: pointer;
|
||||||
-webkit-tap-highlight-color: transparent; }
|
-webkit-tap-highlight-color: transparent; }
|
||||||
.btn.go { border-color: #e0cc72; color: #e0cc72; }
|
.btn.go { border-color: #e0cc72; color: #e0cc72; }
|
||||||
|
/* The two menu buttons wear generated plates. The LETTERING stays Silkscreen on top rather than
|
||||||
|
being drawn into the art: generated type is a lottery and would not match the rest of the HUD.
|
||||||
|
A text-shadow is what keeps it readable where a drip runs behind it. */
|
||||||
|
.btn.plate, .btn.plate2 { border: none; padding: 16px 10px; background-size: 100% 100%;
|
||||||
|
background-repeat: no-repeat; image-rendering: pixelated;
|
||||||
|
text-shadow: 0 2px 0 rgba(0,0,0,0.85); }
|
||||||
|
.btn.plate { background-image: url('assets/btn_start.png?v=92'); color: #f4e6a6; }
|
||||||
|
.btn.plate2 { background-image: url('assets/btn_tut.png?v=92'); color: #cfd8e6; }
|
||||||
|
#menu .logo { width: min(280px, 70vw); margin-bottom: 22px; }
|
||||||
.btn.back { font-size: 10px; padding: 9px 10px; color: #8a96ab; }
|
.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
|
/* 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. */
|
||||||
@@ -112,8 +121,10 @@
|
|||||||
#tutorial .say { flex: 1; }
|
#tutorial .say { flex: 1; }
|
||||||
#tutorial .say p { margin: 0 0 10px; font: 400 10px Silkscreen, monospace; color: #dfe6f2; line-height: 1.9; }
|
#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 .say em { color: #e0cc72; font-style: normal; }
|
||||||
#tutorial .shot { display: block; max-width: 100%; max-height: 108px; margin: 0 0 8px;
|
/* the panel is 2:1 and it is the point of the page, so it gets the full card width above the
|
||||||
image-rendering: pixelated; }
|
Marvin row rather than being squeezed into the text column beside him */
|
||||||
|
#tutorial .shot { display: block; width: min(360px, 88vw); margin: 0 auto 12px;
|
||||||
|
image-rendering: pixelated; border: 2px solid #2a3140; }
|
||||||
#tutorial .dots { margin: 14px 0 10px; font: 400 10px Silkscreen, monospace; color: #5a6270; letter-spacing: 3px; }
|
#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 { display: flex; gap: 8px; width: min(340px, 84vw); margin: 0 auto; }
|
||||||
#tutorial .acts .btn { margin: 0; flex: 1; }
|
#tutorial .acts .btn { margin: 0; flex: 1; }
|
||||||
@@ -139,9 +150,10 @@
|
|||||||
<p id="build"></p>
|
<p id="build"></p>
|
||||||
</div>
|
</div>
|
||||||
<div id="menu">
|
<div id="menu">
|
||||||
<h2>SPLATTER TENNIS</h2>
|
<!-- the logo IS the menu's headline; the title screen's own art, not a word set in Silkscreen -->
|
||||||
<button class="btn go" id="mStart">START GAME</button>
|
<img class="logo" src="assets/logo.png" alt="Splatter Tennis">
|
||||||
<button class="btn" id="mTut">TUTORIAL</button>
|
<button class="btn go plate" id="mStart">START GAME</button>
|
||||||
|
<button class="btn plate2" id="mTut">TUTORIAL</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- court rows are built from config.COURTS in main.js: adding a court is a table row, never markup -->
|
<!-- court rows are built from config.COURTS in main.js: adding a court is a table row, never markup -->
|
||||||
<div id="courts">
|
<div id="courts">
|
||||||
@@ -155,6 +167,7 @@
|
|||||||
<!-- tutorial pages are built from a table in main.js -->
|
<!-- tutorial pages are built from a table in main.js -->
|
||||||
<div id="tutorial">
|
<div id="tutorial">
|
||||||
<h2>HOW IT WORKS</h2>
|
<h2>HOW IT WORKS</h2>
|
||||||
|
<img class="shot" id="tShot" alt="">
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<img class="marvin" id="tMarvin" src="assets/marvin1.png" alt="">
|
<img class="marvin" id="tMarvin" src="assets/marvin1.png" alt="">
|
||||||
<div class="say" id="tSay"></div>
|
<div class="say" id="tSay"></div>
|
||||||
@@ -167,6 +180,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="main.js?v=91"></script>
|
<script type="module" src="main.js?v=93"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -78,22 +78,22 @@ const TUT = [
|
|||||||
{ pose: 1, say: ["I'm <em>MARVIN</em>. I mop the court.",
|
{ pose: 1, say: ["I'm <em>MARVIN</em>. I mop the court.",
|
||||||
"Lately there is a great deal more to mop.",
|
"Lately there is a great deal more to mop.",
|
||||||
"Let me show you how this goes."] },
|
"Let me show you how this goes."] },
|
||||||
{ pose: 2, shot: 'court_grass', say: ["First it is just <em>TENNIS</em>.",
|
{ pose: 2, shot: 'tut_serve', say: ["First it is just <em>TENNIS</em>.",
|
||||||
"<em>TAP</em> once to toss, tap again to serve.",
|
"<em>TAP</em> once to toss, tap again to serve.",
|
||||||
"<em>FLICK</em> toward where you want it.",
|
"<em>FLICK</em> toward where you want it.",
|
||||||
"<em>DRAG</em> to run — though he mostly runs himself."] },
|
"<em>DRAG</em> to run — though he mostly runs himself."] },
|
||||||
{ pose: 4, say: ["Then, at some point, the other fellow...",
|
{ pose: 4, say: ["Then, at some point, the other fellow...",
|
||||||
"stops being the other fellow.",
|
"stops being the other fellow.",
|
||||||
"Nobody stops the match. Nobody ever does."] },
|
"Nobody stops the match. Nobody ever does."] },
|
||||||
{ pose: 2, shot: 'x_howto', say: ["Now you do not rally. You <em>AIM</em>.",
|
{ pose: 2, shot: 'tut_aim', say: ["Now you do not rally. You <em>AIM</em>.",
|
||||||
"<em>PULL BACK</em> from your man and let go.",
|
"<em>PULL BACK</em> from your man and let go.",
|
||||||
"The further you pull, the harder it lands.",
|
"The further you pull, the harder it lands.",
|
||||||
"A soft one only makes them cross."] },
|
"A soft one only makes them cross."] },
|
||||||
{ pose: 2, shot: 'w_bomb', say: ["I bring tins out to the rack down there.",
|
{ pose: 2, shot: 'tut_rack', say: ["I bring tins out to the rack down there.",
|
||||||
"<em>BOMBS</em>, <em>TRIPLES</em>, <em>FIRE</em>, and the <em>GUN</em>.",
|
"<em>BOMBS</em>, <em>TRIPLES</em>, <em>FIRE</em>, and the <em>GUN</em>.",
|
||||||
"Tap the rack to load one, then serve it.",
|
"Tap the rack to load one, then serve it.",
|
||||||
"Do try not to hit me with them."] },
|
"Do try not to hit me with them."] },
|
||||||
{ pose: 3, say: ["Sometimes the umpire calls order back.",
|
{ pose: 3, shot: 'tut_duel', say: ["Sometimes the umpire calls order back.",
|
||||||
"One point, tennis rules, against one of them.",
|
"One point, tennis rules, against one of them.",
|
||||||
"The lines are live again. Do not miss."] },
|
"The lines are live again. Do not miss."] },
|
||||||
{ pose: 1, say: ["That is the lot. Mind the deep ones.",
|
{ pose: 1, say: ["That is the lot. Mind the deep ones.",
|
||||||
@@ -102,12 +102,14 @@ const TUT = [
|
|||||||
];
|
];
|
||||||
let tutPage = 0;
|
let tutPage = 0;
|
||||||
const tSay = document.getElementById('tSay'), tMarvin = document.getElementById('tMarvin');
|
const tSay = document.getElementById('tSay'), tMarvin = document.getElementById('tMarvin');
|
||||||
|
const tShot = document.getElementById('tShot');
|
||||||
const tDots = document.getElementById('tDots'), tNext = document.getElementById('tNext');
|
const tDots = document.getElementById('tDots'), tNext = document.getElementById('tNext');
|
||||||
function drawTut() {
|
function drawTut() {
|
||||||
const p = TUT[tutPage];
|
const p = TUT[tutPage];
|
||||||
tMarvin.src = `assets/marvin${p.pose}.png?v=91`;
|
tMarvin.src = `assets/marvin${p.pose}.png?v=92`;
|
||||||
tSay.innerHTML = (p.shot ? `<img class="shot" src="assets/${p.shot}.png?v=91" alt="">` : '')
|
tShot.style.display = p.shot ? 'block' : 'none';
|
||||||
+ p.say.map(l => `<p>${l}</p>`).join('');
|
if (p.shot) tShot.src = `assets/${p.shot}.png?v=93`;
|
||||||
|
tSay.innerHTML = p.say.map(l => `<p>${l}</p>`).join('');
|
||||||
tDots.textContent = TUT.map((_, i) => i === tutPage ? '●' : '·').join('');
|
tDots.textContent = TUT.map((_, i) => i === tutPage ? '●' : '·').join('');
|
||||||
tNext.textContent = tutPage === TUT.length - 1 ? 'DONE' : 'NEXT';
|
tNext.textContent = tutPage === TUT.length - 1 ? 'DONE' : 'NEXT';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user