The menu text reduction: the design handoff, built
Sixty words on the wall became a dozen. The handoff's own summary of the rule:
Jersey 15 NAMES (screen titles, rooms, cards), Bangers CALLS exactly once per
screen (the fixture, the primary button, a modal headline), Silkscreen INFORMS
(numbers, labels, chips) — and a sentence survives only where it carries a
decision. Everything else moved into a bar, a pip or the modal that opens with
the mode.
· Jersey 15 bundled subset (9 KB) and Bungee retired after one afternoon
· SPLASH: the plaque is one row, the goal line is gone (the fixture button
below said it already), the two doors carry a number each
· PICK A GAME: rooms are one word, posters are their names, the fixture is a
hero with the only second line — the whole wall fits one screen now
· MATCH SETUP: unnumbered blocks, the meters' legend once instead of a
description per court, no summary footer
· DIFFICULTY: three pips instead of three sentences
· TRAINING: the revanche is a full-width row, rungs are numbers, the list
ends on the league that opens the rest of the ladder
· VS: three paragraphs became YOU / HOST / JOIN
· MODALS: one rule per row (the sub was the lead again in other words), the
blood pool as pips on that same line, and the walk-on's two levels in ONE
face at ONE size — a comparison drawn at two sizes is a headline with a
footnote (Oliver)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4c0ede33db
commit
a331a1ae37
44 files changed
+3840
-187
No files matched your search
+32
-29
@@ -2368,16 +2368,21 @@ function modalRows(m, inner, skipBody) {
|
||||
const rows = [];
|
||||
const push = r => { rows.push(r); return r; };
|
||||
if (m.foe) {
|
||||
push({ kind: 'split', h: 32 });
|
||||
push({ kind: 'split', h: 26 });
|
||||
const sub = [];
|
||||
for (const l of m.foe.sub || []) sub.push(...wrapText(l, F(8, 0), inner));
|
||||
if (sub.length) push({ kind: 'text', lines: sub, dim: true, h: sub.length * 12 + 2 });
|
||||
} else if (m.rules) {
|
||||
for (const r of m.rules) {
|
||||
const lead = wrapText(r.lead, F(8, 700), inner);
|
||||
const sub = r.sub ? wrapText(r.sub, F(8, 0), inner) : [];
|
||||
push({ kind: 'rule', lead, sub, hot: r.hot, pips: r.pips,
|
||||
h: lead.length * 12 + sub.length * 12 + (r.pips ? 12 : 0) + 2 });
|
||||
// ONE RULE, ONE LINE (2026-08-31, the menu-text-reduction handoff). Every row used to carry a
|
||||
// statement and a consequence, and the consequence was usually the statement again in other
|
||||
// words ("YOUR LEAGUE, ROUND BY ROUND" / "WIN IT ALL — MOVE UP A LEAGUE"). The line that
|
||||
// names the RESULT is the one that survives. A rule is a string, or an object when it needs
|
||||
// the threat colour or the blood pool. `pips` ride the SAME line, hard right.
|
||||
for (const raw of m.rules) {
|
||||
const r = typeof raw === 'string' ? { lead: raw } : raw;
|
||||
const room = inner - (r.pips ? r.pips * 9 + 12 : 0);
|
||||
const lead = wrapText(r.lead, F(8, 700), room);
|
||||
push({ kind: 'rule', lead, hot: r.hot, pips: r.pips, h: lead.length * 12 + 4 });
|
||||
}
|
||||
} else if (!skipBody) {
|
||||
const lines = [];
|
||||
@@ -2415,7 +2420,11 @@ function modalLayout(m) {
|
||||
// ...and only when it actually FITS there: a line that wraps in the narrow column belongs under
|
||||
// the head as an ordinary row, not as a ragged column beside a picture
|
||||
const beside = pairs && wrapped.length <= bodyLines.length + 1;
|
||||
const strap = m.foe ? wrapText(m.foe.line, F(8, 0), headW) : beside ? wrapped : [];
|
||||
// ONE line under the title: the body's slogan on a walk-on, the mode's own fact on a rules card
|
||||
// (`m.strap` — LEAGUE 3 · 4 ROUNDS, the round count the fixture button gave up), or the two
|
||||
// lines of an illustrated announcement.
|
||||
const strap = m.foe ? wrapText(m.foe.line, F(8, 0), headW)
|
||||
: m.strap ? wrapText(m.strap, F(8, 0), headW) : beside ? wrapped : [];
|
||||
const headH = Math.max(artH, 22 + strap.length * 12);
|
||||
const rows = modalRows(m, boxW - pad * 2, beside);
|
||||
let h = pad + headH + MODAL_GAP + MODAL_FOOT + MODAL_BTN + pad;
|
||||
@@ -2464,34 +2473,28 @@ function drawModal(m) {
|
||||
} else if (r.kind === 'rule') {
|
||||
ctx.textAlign = 'left';
|
||||
ctx.font = F(8, 700);
|
||||
r.lead.forEach((l, i) => shadowText(l, inX, y + 7 + i * 12, UI.cream));
|
||||
let ry = y + 7 + r.lead.length * 12;
|
||||
// the consequence line: DIM for information, the modal's hot colour only when it threatens
|
||||
ctx.font = F(8, 0);
|
||||
r.sub.forEach((l, i) => shadowText(l, inX, ry + i * 12, r.hot ? col : UI.dim));
|
||||
if (r.pips) { // the pool, in the HUD's own pip language
|
||||
const py = ry + r.sub.length * 12 + 4;
|
||||
// the rule wears the modal's hot colour only when it threatens; otherwise it is a statement
|
||||
r.lead.forEach((l, i) => shadowText(l, inX, y + 8 + i * 12, r.hot ? col : UI.cream));
|
||||
if (r.pips) { // the pool, in the HUD's own pip language, ON THE SAME LINE
|
||||
const px = x + L.boxW - pad - (r.pips - 1) * 9;
|
||||
for (let k = 0; k < r.pips; k++) {
|
||||
ctx.beginPath(); ctx.arc(inX + 3 + k * 9, py, 2.6, 0, 7);
|
||||
ctx.beginPath(); ctx.arc(px + k * 9, y + 8, 2.6, 0, 7);
|
||||
ctx.fillStyle = '#8a1a12'; ctx.fill();
|
||||
ctx.strokeStyle = 'rgba(0,0,0,0.5)'; ctx.lineWidth = 1; ctx.stroke();
|
||||
}
|
||||
}
|
||||
} else if (r.kind === 'split') {
|
||||
// HIS LEVEL AGAINST YOURS — one frame, two figures, a hairline between them. The number
|
||||
// wears the modal's hot colour while the body outranks you and the pick green once it
|
||||
// does not, which is the whole point of showing it.
|
||||
const half = Math.round((L.boxW - pad * 2) / 2), f = m.foe;
|
||||
ctx.strokeStyle = MODAL_RULE; ctx.lineWidth = 1;
|
||||
ctx.beginPath(); ctx.moveTo(inX + half + 0.5, y + 4); ctx.lineTo(inX + half + 0.5, y + r.h - 4);
|
||||
ctx.stroke();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.font = F(7, 0);
|
||||
shadowText('ITS LEVEL', inX + half / 2, y + 8, UI.dim);
|
||||
shadowText('YOURS', inX + half * 1.5, y + 8, UI.dim);
|
||||
ctx.font = F(13, 700);
|
||||
shadowText(String(f.lvl), inX + half / 2, y + 23, f.lvl > f.mine ? col : UI.pick);
|
||||
shadowText(String(f.mine), inX + half * 1.5, y + 23, UI.cream);
|
||||
// HIS LEVEL AGAINST YOURS, on ONE line: his on the left, yours hard right. SAME FACE, SAME
|
||||
// SIZE for both (Oliver) — a comparison drawn at two sizes is not a comparison, it is a
|
||||
// headline with a footnote. Only the COLOUR differs, and only where it means something: his
|
||||
// number wears the modal's hot colour while he outranks you, the pick green once he does not.
|
||||
const f = m.foe;
|
||||
ctx.font = F(10, 700);
|
||||
ctx.textAlign = 'left';
|
||||
shadowText('LEVEL ' + f.lvl, inX, y + 9, f.lvl > f.mine ? col : UI.pick);
|
||||
ctx.textAlign = 'right';
|
||||
shadowText('YOURS ' + f.mine, x + L.boxW - pad, y + 9, UI.dim);
|
||||
ctx.textAlign = 'left';
|
||||
}
|
||||
y += r.h + MODAL_GAP;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user