fix(wordle): render letters as emoji-class characters for exact alignment

Previous fullwidth-Latin approach (U+FF21..FF3A) failed on mobile
Telegram clients because their monospace fonts don't ship fullwidth
glyphs — the codepoints fall back to base Latin at 1 cell, making the
letter row half as wide as the marker row.

Switch to Negative Squared Latin Capital Letters (U+1F170..1F189,
🅰🅱🅲..🆉) — these are emoji-class characters, so both rows are
drawn by the same emoji font at the same cell width. Column alignment
becomes a property of Unicode, not of the client's monospace font.

  🟩🟨⬜🟩🟩
  🅲🆁🅰🅽🅴
This commit is contained in:
tiennm99 committed 2026-04-20 22:45:49 +07:00
1 parent 0c3eaf3885
commit c86227faf9
1 file changed
+13 -9
+13 -9
View File
@@ -15,22 +15,26 @@
const MARKER = { correct: "🟩", partial: "🟨", wrong: "⬜" };
// In a Telegram <pre> block, color-square emoji render at ~2 monospace cells
// wide, while ASCII letters render at 1 cell — so a plain "A B C" row never
// lines up under the markers. Fullwidth Latin (U+FF21..U+FF3A) is East Asian
// Width = Fullwidth, which renders at exactly 2 cells per character and
// matches the emoji column width 1-to-1.
const FULLWIDTH_OFFSET = 0xff21 - 0x41; // 'A' (0x41) → 'A' (0xFF21)
// Alignment under Telegram <pre>: the color markers are emoji; to guarantee
// the letter row is the same width, render letters as emoji too. Unicode
// block "Enclosed Alphanumeric Supplement" has emoji-class capitals at
// U+1F170..FF189 (🅰🅱🅲..🆉 — "Negative Squared Latin Capital Letter").
// Because both rows draw from the emoji font, column widths match 1-to-1
// on every client, regardless of the monospace font shipped with it.
const EMOJI_A = 0x1f170;
const ASCII_A = 0x41;
function toFullwidthUpper(ch) {
function toEmojiLetter(ch) {
const code = ch.toUpperCase().charCodeAt(0);
if (code >= 0x41 && code <= 0x5a) return String.fromCharCode(code + FULLWIDTH_OFFSET);
if (code >= ASCII_A && code <= 0x5a) {
return String.fromCodePoint(EMOJI_A + (code - ASCII_A));
}
return ch;
}
function rowPair(results) {
const markers = results.map((r) => MARKER[r.result] ?? "⬜").join("");
const letters = results.map((r) => toFullwidthUpper(r.letter)).join("");
const letters = results.map((r) => toEmojiLetter(r.letter)).join("");
return `${markers}\n${letters}`;
}