From c86227faf973d8f553da8186e0527cf397a6cd39 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Mon, 20 Apr 2026 22:45:49 +0700 Subject: [PATCH] fix(wordle): render letters as emoji-class characters for exact alignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. 🟩🟨⬜🟩🟩 🅲🆁🅰🅽🅴 --- src/modules/wordle/render.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/modules/wordle/render.js b/src/modules/wordle/render.js index 5210794..a35b1e6 100644 --- a/src/modules/wordle/render.js +++ b/src/modules/wordle/render.js @@ -15,22 +15,26 @@ const MARKER = { correct: "🟩", partial: "🟨", wrong: "⬜" }; -// In a Telegram
 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 
: 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}`;
 }