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)}
{tile.letter.toUpperCase()}
- {#if !tile.blank}{tile.value}{/if}
+ {#if !tile.blank}
+ {tile.value}
+ {:else if usesStarBlank(variant)}
+ {BLANK_STAR}
+ {/if}
{/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}