From e3e4cedc779137f2dea23fe91ccae612505abd31 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 22 Jun 2026 11:52:00 +0200 Subject: [PATCH 1/4] =?UTF-8?q?feat(ui):=20Erudit=20blank=20tiles=20carry?= =?UTF-8?q?=20the=20star=20(=E2=9C=BB)=20mark?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Erudit variant's blank is the "звёздочка", so render it with a star. An empty rack blank (and its drag ghost) shows ✻ centred; a placed blank keeps its designated letter and carries ✻ where the (absent) point value sits — on the board and in the Stats best-move tiles. The Scrabble variants are unchanged. Gated by usesStarBlank() in lib/variants.ts. --- docs/UI_DESIGN.md | 5 ++++- ui/src/components/WordTiles.svelte | 22 ++++++++++++++++------ ui/src/game/Board.svelte | 11 ++++++++++- ui/src/game/Game.svelte | 4 ++-- ui/src/game/Rack.svelte | 17 +++++++++++++++-- ui/src/lib/variants.test.ts | 15 +++++++++++++++ ui/src/lib/variants.ts | 14 ++++++++++++++ ui/src/screens/Stats.svelte | 2 +- 8 files changed, 77 insertions(+), 13 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index a488475..3c24066 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -109,7 +109,10 @@ dismisses as soon as the lobby is ready. The pure layout and timing live in `lib ## Tiles & board - **Tiles**: the letter sits in the **top-left** corner (offset a touch more than the - value), the point value bottom-right; blanks show no value. + value), the point value bottom-right; blanks show no value. In **Erudit** the blank is the + "звёздочка" (star) chip: an unplaced blank shows the star (`✻`, U+273B) centred on the rack + tile, and a placed blank carries it in the value corner; the Scrabble variants leave the + blank unmarked (`usesStarBlank` in `lib/variants.ts`). - **Board zoom** (`Board.svelte`): a two-state zoom (full 15×15 ↔ ~9 cells) by **growing the board's width** inside a fixed-size viewport (a real layout change → native scroll that works consistently across browsers; no `transform`, which broke scrolling diff --git a/ui/src/components/WordTiles.svelte b/ui/src/components/WordTiles.svelte index 1a70ff4..c170558 100644 --- a/ui/src/components/WordTiles.svelte +++ b/ui/src/components/WordTiles.svelte @@ -1,12 +1,14 @@ @@ -15,7 +17,11 @@ {#each word as tile, i (i)} {/each} @@ -50,4 +56,8 @@ font-size: 7px; font-weight: 600; } + /* A placed Erudit blank ("звёздочка") shows its star where the (absent) point value sits. */ + .blankmark { + font-size: 10px; + } diff --git a/ui/src/game/Board.svelte b/ui/src/game/Board.svelte index 11256b9..024c0e3 100644 --- a/ui/src/game/Board.svelte +++ b/ui/src/game/Board.svelte @@ -4,6 +4,7 @@ import type { Premium } from '../lib/premiums'; import { valueForLetter } from '../lib/alphabet'; import type { Variant } from '../lib/model'; + import { usesStarBlank, BLANK_STAR } from '../lib/variants'; import { bonusLabel, type BoardLabelMode } from '../lib/boardlabels'; import type { Locale } from '../lib/i18n/catalog'; @@ -254,7 +255,11 @@ > {#if letter} {letter} - {#if !blank}{valueForLetter(variant, letter)}{/if} + {#if !blank} + {valueForLetter(variant, letter)} + {:else if usesStarBlank(variant)} + {BLANK_STAR} + {/if} {:else if r === centre.row && c === centre.col} {:else if bl?.kind === 'single'} @@ -410,6 +415,10 @@ font-size: 2.4cqw; font-weight: 600; } + /* A placed Erudit blank ("звёздочка") shows its star where the (absent) point value sits. */ + .blankmark { + font-size: 3.2cqw; + } .star { position: absolute; inset: 0; diff --git a/ui/src/game/Game.svelte b/ui/src/game/Game.svelte index d05ffd9..cb967cc 100644 --- a/ui/src/game/Game.svelte +++ b/ui/src/game/Game.svelte @@ -17,7 +17,7 @@ import { badgeKind } from '../lib/unread'; import { historyGrid } from '../lib/history'; import { centre, premiumGrid } from '../lib/premiums'; - import { variantNameKey } from '../lib/variants'; + import { variantNameKey, usesStarBlank, BLANK_STAR } from '../lib/variants'; import { alphabetLetters, hasAlphabet } from '../lib/alphabet'; import { hintsLeft } from '../lib/hints'; import { shareOrDownloadGcg } from '../lib/share'; @@ -1293,7 +1293,7 @@ {#if drag}
- {drag.blank ? '' : drag.letter} + {drag.blank ? (usesStarBlank(variant) ? BLANK_STAR : '') : drag.letter}
{/if} diff --git a/ui/src/game/Rack.svelte b/ui/src/game/Rack.svelte index efe7cee..7289a7e 100644 --- a/ui/src/game/Rack.svelte +++ b/ui/src/game/Rack.svelte @@ -3,6 +3,7 @@ import { BLANK } from '../lib/placement'; import { valueForLetter } from '../lib/alphabet'; import type { Variant } from '../lib/model'; + import { usesStarBlank, BLANK_STAR } from '../lib/variants'; let { slots, @@ -66,8 +67,12 @@ animate:hop={shuffling} onpointerdown={(e) => ondown(e, slot.index)} > - {slot.letter === BLANK ? '' : slot.letter} - {#if slot.letter !== BLANK}{valueForLetter(variant, slot.letter)}{/if} + {#if slot.letter === BLANK} + {#if usesStarBlank(variant)}{BLANK_STAR}{/if} + {:else} + {slot.letter} + {valueForLetter(variant, slot.letter)} + {/if} {/each} @@ -133,4 +138,12 @@ font-size: 0.7rem; font-weight: 600; } + /* Erudit's blank ("звёздочка") shows its star centred on the otherwise empty tile face. */ + .star { + position: absolute; + inset: 0; + display: grid; + place-items: center; + font-size: 1.5rem; + } diff --git a/ui/src/lib/variants.test.ts b/ui/src/lib/variants.test.ts index e5e5712..e8a208d 100644 --- a/ui/src/lib/variants.test.ts +++ b/ui/src/lib/variants.test.ts @@ -5,6 +5,8 @@ import { availableVariants, supportsMultipleWordsToggle, multipleWordsForRequest, + usesStarBlank, + BLANK_STAR, } from './variants'; describe('ALL_VARIANTS', () => { @@ -53,3 +55,16 @@ describe('multipleWordsForRequest', () => { expect(multipleWordsForRequest('scrabble_en', true)).toBe(true); }); }); + +describe('usesStarBlank', () => { + it('marks the blank with a star for Erudit only', () => { + expect(usesStarBlank('erudit_ru')).toBe(true); + expect(usesStarBlank('scrabble_ru')).toBe(false); + expect(usesStarBlank('scrabble_en')).toBe(false); + }); + + it('BLANK_STAR is the heavy teardrop-spoked asterisk (U+273B)', () => { + expect(BLANK_STAR).toBe('✻'); + expect(BLANK_STAR.codePointAt(0)).toBe(0x273b); + }); +}); diff --git a/ui/src/lib/variants.ts b/ui/src/lib/variants.ts index f317a50..5981007 100644 --- a/ui/src/lib/variants.ts +++ b/ui/src/lib/variants.ts @@ -48,6 +48,20 @@ export const VARIANT_FLAG: Record = { // ru -> Russian + Эрудит. export const VARIANT_LANGUAGE: Record = { scrabble_en: 'en', scrabble_ru: 'ru', erudit_ru: 'ru' }; +// BLANK_STAR is the glyph drawn on an Эрудит blank tile: the variant's blank is the +// "звёздочка" (star) chip, so it carries a star rather than a bare face. U+273B HEAVY +// TEARDROP-SPOKED ASTERISK. +export const BLANK_STAR = '✻'; + +// usesStarBlank reports whether a variant marks its blank tiles with BLANK_STAR. Only +// Эрудит does: an empty rack blank shows the star centred, and a placed blank carries it +// in the value corner (the corner is free — a blank has no point value). The Scrabble +// variants leave the blank unmarked (an empty rack face; a placed blank shown by its +// designated letter alone). +export function usesStarBlank(v: Variant): boolean { + return v === 'erudit_ru'; +} + // availableVariants gates ALL_VARIANTS by the player's variant preferences (the set // they enabled in Settings). An empty or absent set is ungated (returns every variant) // — a safety fallback; a real profile always carries at least one preference. diff --git a/ui/src/screens/Stats.svelte b/ui/src/screens/Stats.svelte index 74c48f1..56f9672 100644 --- a/ui/src/screens/Stats.svelte +++ b/ui/src/screens/Stats.svelte @@ -73,7 +73,7 @@ {#each bestMoves as bm (bm.variant)} {t(variantNameKey(bm.variant))} {bm.score} - + {/each} From a393561d790375a0d1045dbe9cd6de13bfd4564d Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 22 Jun 2026 12:11:35 +0200 Subject: [PATCH 2/4] fix(ui): align the Erudit blank star with neighbouring tiles Rack: the empty-blank star is now top-anchored level with the letters and a touch larger (was centred, sitting low). Board and Stats best-move tiles: the placed-blank star's ink is centred on the value digits' line (was slightly high). CSS-only nudges; pixel offsets measured against the rendered glyphs. --- ui/src/components/WordTiles.svelte | 6 ++++-- ui/src/game/Board.svelte | 6 ++++-- ui/src/game/Rack.svelte | 12 +++++++----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/ui/src/components/WordTiles.svelte b/ui/src/components/WordTiles.svelte index c170558..6bfb652 100644 --- a/ui/src/components/WordTiles.svelte +++ b/ui/src/components/WordTiles.svelte @@ -56,8 +56,10 @@ font-size: 7px; font-weight: 600; } - /* A placed Erudit blank ("звёздочка") shows its star where the (absent) point value sits. */ + /* A placed Erudit blank ("звёздочка") shows its star where the (absent) point value sits, + its ink kept on the value digits' line (mirrors the board tile). */ .blankmark { - font-size: 10px; + font-size: 8px; + bottom: 0; } diff --git a/ui/src/game/Board.svelte b/ui/src/game/Board.svelte index 024c0e3..a229155 100644 --- a/ui/src/game/Board.svelte +++ b/ui/src/game/Board.svelte @@ -415,9 +415,11 @@ font-size: 2.4cqw; font-weight: 600; } - /* A placed Erudit blank ("звёздочка") shows its star where the (absent) point value sits. */ + /* A placed Erudit blank ("звёздочка") shows its star where the (absent) point value sits, + its ink centred on the same line as a neighbouring tile's value digit. */ .blankmark { - font-size: 3.2cqw; + font-size: 2.8cqw; + bottom: 0; } .star { position: absolute; diff --git a/ui/src/game/Rack.svelte b/ui/src/game/Rack.svelte index 7289a7e..448718b 100644 --- a/ui/src/game/Rack.svelte +++ b/ui/src/game/Rack.svelte @@ -138,12 +138,14 @@ font-size: 0.7rem; font-weight: 600; } - /* Erudit's blank ("звёздочка") shows its star centred on the otherwise empty tile face. */ + /* Erudit's blank ("звёздочка") shows its star horizontally centred on the otherwise empty + tile face, top-aligned with the neighbouring letters (slightly larger so it reads). */ .star { position: absolute; - inset: 0; - display: grid; - place-items: center; - font-size: 1.5rem; + top: 8%; + left: 0; + right: 0; + text-align: center; + font-size: 1.7rem; } From 81680a1d5e60adeb48d3b977342c3e819d00c731 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 22 Jun 2026 12:22:48 +0200 Subject: [PATCH 3/4] fix(ui): raise the rack blank star to centre on the letters Top-aligning it still read a touch low; move the empty-blank star up (top 8% -> 0.5%) so its ink centres against the rack letters' block, matching the board tile's centred mark. Size unchanged. --- ui/src/game/Rack.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/src/game/Rack.svelte b/ui/src/game/Rack.svelte index 448718b..98ac3c4 100644 --- a/ui/src/game/Rack.svelte +++ b/ui/src/game/Rack.svelte @@ -139,10 +139,11 @@ font-weight: 600; } /* Erudit's blank ("звёздочка") shows its star horizontally centred on the otherwise empty - tile face, top-aligned with the neighbouring letters (slightly larger so it reads). */ + tile face; the small top offset centres its ink against the neighbouring letters' block + (it is slightly larger than them so it reads). */ .star { position: absolute; - top: 8%; + top: 0.5%; left: 0; right: 0; text-align: center; From 62f66735a35ed2d025dac24024128540090aeee0 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 22 Jun 2026 12:55:06 +0200 Subject: [PATCH 4/4] fix(ui): nudge the rack blank star up a pixel By eye the centred star still read a hair low; subtract 1px from its top offset (top: calc(0.5% - 1px)). --- ui/src/game/Rack.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/game/Rack.svelte b/ui/src/game/Rack.svelte index 98ac3c4..ec81591 100644 --- a/ui/src/game/Rack.svelte +++ b/ui/src/game/Rack.svelte @@ -139,11 +139,11 @@ font-weight: 600; } /* Erudit's blank ("звёздочка") shows its star horizontally centred on the otherwise empty - tile face; the small top offset centres its ink against the neighbouring letters' block - (it is slightly larger than them so it reads). */ + tile face; the top offset centres its ink against the neighbouring letters' block, nudged + up a pixel to sit right by eye (it is slightly larger than them so it reads). */ .star { position: absolute; - top: 0.5%; + top: calc(0.5% - 1px); left: 0; right: 0; text-align: center;