Stage 7 polish: game rework + board zoom + tests (Parts D/E/F/I)
- Board: fixed-viewport transform-scale zoom (animated) with counter-scaled cqw labels, corner letters, bonus-label modes (boardlabels), contrasting grid lines
- Game: Screen shell + game tab-bar (Draw/Skip/Hint/Shuffle) via HoldConfirm popovers; MakeMove 🏁 + compact popup; rack collapses used slots; hint places tiles on board (placementFromHint) + no_hint_available toast; Scores:N replaces Hints; history slide-down (swipe/click, scroll-locked); check-word alphabet/length limit + in-memory cache + 5s throttle
- backend: no_hint_available result code split + test
- vitest: banner rotator + linkify, resultBadge, boardlabels, placementFromHint (29 tests); Playwright smoke updated; prod bundle ~74 KB gzip
This commit is contained in:
+26
-25
@@ -15,45 +15,41 @@
|
||||
selected: number | null;
|
||||
ondown: (e: PointerEvent, index: number) => void;
|
||||
} = $props();
|
||||
|
||||
// Used slots are hidden (the rack shifts left, freeing room on the right for the
|
||||
// MakeMove control); the slot still exists in the model for per-tile recall.
|
||||
const visible = $derived(slots.filter((s) => !s.used));
|
||||
</script>
|
||||
|
||||
<div class="rack">
|
||||
{#each slots as slot (slot.index)}
|
||||
{#if slot.used}
|
||||
<span class="slot empty"></span>
|
||||
{:else}
|
||||
<button
|
||||
class="slot tile"
|
||||
class:selected={selected === slot.index}
|
||||
data-rack-index={slot.index}
|
||||
onpointerdown={(e) => ondown(e, slot.index)}
|
||||
>
|
||||
<span class="letter">{slot.letter === BLANK ? '' : slot.letter}</span>
|
||||
{#if slot.letter !== BLANK}<span class="val">{tileValue(variant, slot.letter)}</span>{/if}
|
||||
</button>
|
||||
{/if}
|
||||
{#each visible as slot (slot.index)}
|
||||
<button
|
||||
class="tile"
|
||||
class:selected={selected === slot.index}
|
||||
data-rack-index={slot.index}
|
||||
onpointerdown={(e) => ondown(e, slot.index)}
|
||||
>
|
||||
<span class="letter">{slot.letter === BLANK ? '' : slot.letter}</span>
|
||||
{#if slot.letter !== BLANK}<span class="val">{tileValue(variant, slot.letter)}</span>{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.rack {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
.slot {
|
||||
aspect-ratio: 1;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.empty {
|
||||
background: var(--surface-2);
|
||||
border: 1px dashed var(--border);
|
||||
align-items: center;
|
||||
}
|
||||
.tile {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
width: min(12.5vw, 46px);
|
||||
aspect-ratio: 1;
|
||||
background: var(--tile-bg);
|
||||
color: var(--tile-text);
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
box-shadow: inset 0 -3px 0 var(--tile-edge);
|
||||
font-weight: 700;
|
||||
font-size: 1.4rem;
|
||||
@@ -64,9 +60,14 @@
|
||||
outline: 3px solid var(--accent);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
.letter {
|
||||
position: absolute;
|
||||
top: 8%;
|
||||
left: 14%;
|
||||
}
|
||||
.val {
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
right: 4px;
|
||||
bottom: 1px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
|
||||
Reference in New Issue
Block a user