The card lies flat, and a popup owns its screen

- icon LEFT of the text: every card is 62px at a fixed top, fully over
  the crowd — the stacked layout hung its lower third over live court,
  and the art draws bigger this way, not smaller
- nothing draws or ticks under a modal: a card raised in the same beat
  used to burn out dimmed beneath it — it holds and plays after

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-08 21:10:11 +02:00
1 parent 1337048a85
commit 911035af00
4 files changed
+39 -26

No files matched your search

+21 -19
View File
@@ -1178,30 +1178,29 @@ function drawRack() {
// CROWD, below the scoreboard block, above the ad strip. Nothing transient sits on the court
// anymore — a card at centre court covered the very play it was announcing, non-blocking or not.
const AD_TOP = () => Math.round(proj(0, 0, -6).y) - AD_H; // top of the courtside ad strip
// The band has ROWS now: the flash line owns 68..90 (FLASH_Y, Bangers 18) and the countdown panel
// the strip under it, so a card may never climb above 96 — a tall icon card used to reach the
// flash line and the two printed over each other, which was the "spamming over each other" report.
// The floor wins over the ad-strip anchor: a too-tall card overlaps the ads (decoration) instead.
const bandTop = h => Math.max(96, AD_TOP() - h - 4);
// The band has ROWS: the flash line owns 68..90 (FLASH_Y, Bangers 18), the countdown panel the
// strip under it, and the card a FIXED slot below both. The card is ONE compact height now, icon
// or not — the icon sits LEFT of the text instead of stacking above it. Stacked, an icon card ran
// 102 px tall and its lower third hung over live court; side-by-side it is 62 px flat, sits fully
// over the crowd, and the art actually draws BIGGER than the stacked layout gave it.
const CARD_TOP = 96, CARD_H = 62;
function drawCard(title, sub1, sub2, color, icon) {
// An ICON (the won-duel tin) needs its own room above the title, so the band grows for it
// rather than the art crowding the type.
const img = icon && SPR[icon], ih = img ? 34 : 0;
const y0 = bandTop(68 + ih);
ctx.fillStyle = 'rgba(10,6,8,0.72)'; ctx.fillRect(0, y0, W, 68 + ih);
ctx.textAlign = 'center';
if (img) { // the tin itself, at its own aspect, sat on the title
const w = Math.round(img.width * (ih - 6) / img.height);
const img = icon && SPR[icon];
ctx.fillStyle = 'rgba(10,6,8,0.72)'; ctx.fillRect(0, CARD_TOP, W, CARD_H);
if (img) { // left of the text block, vertically centred, boxed so a wide tin can't reach the type
const k = Math.min((CARD_H - 10) / img.height, 58 / img.width);
const iw = Math.round(img.width * k), ih = Math.round(img.height * k);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, Math.round(W / 2 - w / 2), y0 + 3, w, ih - 6);
ctx.drawImage(img, 8, Math.round(CARD_TOP + (CARD_H - ih) / 2), iw, ih);
}
// the title still SHOUTS (Bangers) but at 22, not 30 — up here it shares the screen with the
// scoreboard and the ads, and 30 read as oversized the moment it left centre court
ctx.font = B(22); shadowText(title, W / 2, y0 + ih + 8, color);
ctx.textAlign = 'center';
ctx.font = B(22); shadowText(title, W / 2, CARD_TOP + 4, color);
ctx.font = F(8);
if (sub1) shadowText(sub1, W / 2, y0 + ih + 36, UI.cream);
if (sub2) shadowText(sub2, W / 2, y0 + ih + 50, UI.cream);
if (sub1) shadowText(sub1, W / 2, CARD_TOP + 34, UI.cream);
if (sub2) shadowText(sub2, W / 2, CARD_TOP + 48, UI.cream);
}
// A GHOST HAND, fingertip at (x, y) — the tutorial finger every gesture hint shares. A drawn
@@ -1825,7 +1824,10 @@ export function render() {
// The free-floating register: no panel, one fixed slot, shouted. PERFECT, GAME N, DOUBLE KILL,
// RETURNED, CHANGEOVER, weapon labels — everything that used to be plain white 12px text sitting
// directly on the crowd. It keeps its 1px jitter; the shadow is what makes it legible anywhere.
if (G.flash) {
// ...and neither the flash nor the card draws UNDER a modal: the popup owns the screen, and a
// band message half-visible through its dim was clutter saying nothing. Their clocks are frozen
// behind the freeze too (see update), so both play out in full once the popup is dismissed.
if (G.flash && !G.modal) {
// 18, not 24: at the card's near-size a transient one-liner read as a milestone announcement,
// and half of what lands in this slot (PERFECT, RETURNED, DOUBLE KILL) is only ever a footnote
// to something you already watched happen. The register is right; the volume was not.
@@ -1833,7 +1835,7 @@ export function render() {
shadowText(G.flash.text, W / 2, FLASH_Y + Math.sin(G.flash.t * 20) * 1, '#ffffff');
}
// the SET milestone card (the duel's own announcement is a real modal now, not a timed card)
if (G.card && G.card.t > 0 && !(G.card.wait > 0)) // `wait` holds it OFF the screen (a boss dying under it)
if (G.card && !G.modal && G.card.t > 0 && !(G.card.wait > 0)) // `wait` holds it OFF the screen (a boss dying under it)
drawCard(G.card.title, G.card.sub1, G.card.sub2, G.card.color, G.card.icon);
if (TUNE.missHud && G.lastMiss && G.idleT - G.lastMiss.at < 3.5) { // last miss reason, live
ctx.font = F(8); ctx.fillStyle = '#ff9060';