diff --git a/HANDOFF.md b/HANDOFF.md index d5a409f..ffffecd 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -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) 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 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. diff --git a/assets/btn_start.png b/assets/btn_start.png new file mode 100644 index 0000000..1a8f0a7 Binary files /dev/null and b/assets/btn_start.png differ diff --git a/assets/btn_tut.png b/assets/btn_tut.png new file mode 100644 index 0000000..2184258 Binary files /dev/null and b/assets/btn_tut.png differ diff --git a/assets/tut_aim.png b/assets/tut_aim.png new file mode 100644 index 0000000..ba74808 Binary files /dev/null and b/assets/tut_aim.png differ diff --git a/assets/tut_duel.png b/assets/tut_duel.png new file mode 100644 index 0000000..c94f7f9 Binary files /dev/null and b/assets/tut_duel.png differ diff --git a/assets/tut_rack.png b/assets/tut_rack.png new file mode 100644 index 0000000..d2cb8a8 Binary files /dev/null and b/assets/tut_rack.png differ diff --git a/assets/tut_serve.png b/assets/tut_serve.png new file mode 100644 index 0000000..7ec8f52 Binary files /dev/null and b/assets/tut_serve.png differ diff --git a/index.html b/index.html index 3ef920e..a3032d9 100644 --- a/index.html +++ b/index.html @@ -93,6 +93,15 @@ font: 400 12px Silkscreen, monospace; letter-spacing: 1px; cursor: pointer; -webkit-tap-highlight-color: transparent; } .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; } /* 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. */ @@ -112,8 +121,10 @@ #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; } + /* the panel is 2:1 and it is the point of the page, so it gets the full card width above the + 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 .acts { display: flex; gap: 8px; width: min(340px, 84vw); margin: 0 auto; } #tutorial .acts .btn { margin: 0; flex: 1; } @@ -139,9 +150,10 @@

@@ -155,6 +167,7 @@

HOW IT WORKS

+
@@ -167,6 +180,6 @@
- + diff --git a/main.js b/main.js index c92345c..12d9d44 100644 --- a/main.js +++ b/main.js @@ -78,22 +78,22 @@ const TUT = [ { pose: 1, say: ["I'm MARVIN. I mop the court.", "Lately there is a great deal more to mop.", "Let me show you how this goes."] }, - { pose: 2, shot: 'court_grass', say: ["First it is just TENNIS.", + { pose: 2, shot: 'tut_serve', say: ["First it is just TENNIS.", "TAP once to toss, tap again to serve.", "FLICK toward where you want it.", "DRAG to run — though he mostly runs himself."] }, { pose: 4, say: ["Then, at some point, the other fellow...", "stops being the other fellow.", "Nobody stops the match. Nobody ever does."] }, - { pose: 2, shot: 'x_howto', say: ["Now you do not rally. You AIM.", + { pose: 2, shot: 'tut_aim', say: ["Now you do not rally. You AIM.", "PULL BACK from your man and let go.", "The further you pull, the harder it lands.", "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.", "BOMBS, TRIPLES, FIRE, and the GUN.", "Tap the rack to load one, then serve it.", "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.", "The lines are live again. Do not miss."] }, { pose: 1, say: ["That is the lot. Mind the deep ones.", @@ -102,12 +102,14 @@ const TUT = [ ]; let tutPage = 0; const tSay = document.getElementById('tSay'), tMarvin = document.getElementById('tMarvin'); +const tShot = document.getElementById('tShot'); const tDots = document.getElementById('tDots'), tNext = document.getElementById('tNext'); function drawTut() { const p = TUT[tutPage]; - tMarvin.src = `assets/marvin${p.pose}.png?v=91`; - tSay.innerHTML = (p.shot ? `` : '') - + p.say.map(l => `

${l}

`).join(''); + tMarvin.src = `assets/marvin${p.pose}.png?v=92`; + tShot.style.display = p.shot ? 'block' : 'none'; + if (p.shot) tShot.src = `assets/${p.shot}.png?v=93`; + tSay.innerHTML = p.say.map(l => `

${l}

`).join(''); tDots.textContent = TUT.map((_, i) => i === tutPage ? '●' : '·').join(''); tNext.textContent = tutPage === TUT.length - 1 ? 'DONE' : 'NEXT'; }