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:
1 parent
7fb595ca22
commit
00f121bec7
5 files changed
+87
-10
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user