The walk-on gets its own face: mugshot masthead, the measure, the round strip — no more wall of text

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-30 16:22:20 +02:00
1 parent 7fb595ca22
commit 00f121bec7
5 files changed
+87 -10

No files matched your search

+60
View File
@@ -2341,6 +2341,65 @@ function drawFlickHint() {
const BACK_C = 1.70158;
const easeBack = p => 1 + (BACK_C + 1) * (p - 1) ** 3 + BACK_C * (p - 1) ** 2;
// THE WALK-ON'S OWN FACE (2026-08-29, Oliver: the opponent card was "a wall of text" — six kinds
// of information in one line stack). Designated zones instead: the MASTHEAD (mugshot beside the
// name with the slogan under it), THE MEASURE (its level boxed against yours — the number wears
// the modal's hot red while the body outranks you, the pick green once it does not), and the
// round/lesson strip. Same machinery as every modal otherwise: the entrance lanes, the frozen
// court, the blinking tap (drawModal below). m.title/m.body still carry the same words for the
// suite and any legacy reader — this is a LAYOUT, not new state.
function drawFoeModal(m, t, dx) {
const f = m.foe, img = f.head && SPR[f.head];
const boxW = Math.min(W - 24, 250), bx = Math.round(W / 2 - boxW / 2);
const mugH = Math.round(Math.min(56, H * 0.22));
const mugW = img ? Math.round(mugH * img.width / img.height) : 0;
const slogan = wrapText(f.line, F(8, 0), boxW - (mugW ? mugW + 12 : 0)); // the shared wrapper sets the font
const headH = Math.max(mugH, 26 + slogan.length * 12);
const measureH = 36, gap = 14;
const subH = f.sub.length ? f.sub.length * 12 + 4 : 0;
const blockH = headH + gap + measureH + (subH ? gap - 4 + subH : 0);
let y = Math.round(H / 2 - blockH / 2);
// THE MASTHEAD flies from the right as one piece
const hx = dx(0, 1);
const nx = bx + (mugW ? mugW + 12 : 0) + hx;
if (img) { ctx.imageSmoothingEnabled = false; ctx.drawImage(img, bx + hx, y, mugW, mugH); }
ctx.textAlign = 'left';
ctx.font = B(20);
ctx.fillStyle = m.color || UI.hot;
ctx.fillText(f.name, nx, y + 10);
ctx.font = F(8, 0);
slogan.forEach((l, i) => shadowText(l, nx, y + 28 + i * 12, UI.cream));
y += headH + gap;
// THE MEASURE flies from the left: two boxed figures sharing one frame (the .figs language)
const mx = dx(1, -1);
const half = Math.round(boxW / 2);
panel(bx + mx, y, boxW, measureH, 3, 'rgba(8,26,18,0.85)', 'rgba(120,150,130,0.4)');
ctx.strokeStyle = 'rgba(120,150,130,0.4)'; ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(bx + mx + half + 0.5, y + 4); ctx.lineTo(bx + mx + half + 0.5, y + measureH - 4);
ctx.stroke();
ctx.textAlign = 'center';
ctx.font = F(7, 0);
shadowText('ITS LEVEL', bx + mx + half / 2, y + 10, UI.dim);
shadowText('YOURS', bx + mx + half * 1.5, y + 10, UI.dim);
ctx.font = F(13, 700);
shadowText(String(f.lvl), bx + mx + half / 2, y + 25, f.lvl > f.mine ? (m.color || UI.hot) : UI.pick);
shadowText(String(f.mine), bx + mx + half * 1.5, y + 25, UI.cream);
y += measureH;
// the round / the lesson, from the left a beat later
if (f.sub.length) {
y += gap - 4;
ctx.font = F(8, 0);
f.sub.forEach((l, i) => shadowText(l, W / 2 + dx(2, -1), y + 6 + i * 12, UI.dim));
}
if (t >= 1 && Math.floor(G.idleT * 2) % 2) {
ctx.font = F(8);
shadowText(IS_TOUCH ? 'TAP TO CONTINUE' : 'PRESS ANY KEY', W / 2,
Math.round(H / 2 + blockH / 2 + 24), UI.warm);
}
ctx.textBaseline = 'top';
}
function drawModal(m) {
// The entrance (m.in, 0..1, ticked in gameplay's update behind the world freeze): the icon and
// title fly in from the RIGHT, the body lines cascade in from the LEFT one after another, each
@@ -2357,6 +2416,7 @@ function drawModal(m) {
ctx.fillStyle = `rgba(8,6,10,${(0.82 * Math.min(1, t / 0.35)).toFixed(3)})`; // the dim washes in first
ctx.fillRect(0, 0, W, H);
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
if (m.foe) return drawFoeModal(m, t, dx);
const body = m.body || [], img = m.icon && SPR[m.icon];
// cap the illustration against the viewport, not just the asset: at 270px tall (landscape) a
// portrait-sized icon pushed the TAP prompt down onto the weapon rack