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}