word-waltz / commit
fix styling
src/components/WordleBoard.ts | 84 +++++++++++++++++++++++++++++-------------- src/index.html | 2 +- src/styles/main.css | 32 +++++++++++++++-- 3 files changed, 88 insertions(+), 30 deletions(-) diff --git a/src/components/WordleBoard.ts b/src/components/WordleBoard.ts index 604c412..35ae393 100644 --- a/src/components/WordleBoard.ts +++ b/src/components/WordleBoard.ts @@ -145,6 +145,37 @@ export function mountWordleBoard(container: HTMLElement, props: WordleBoardProps }); } + // How long one tile flip takes, and the stagger between tiles. + const FLIP_MS = 500; + const STAGGER_MS = 100; + + // Reveal a row with staggered flips. Returns a promise that resolves when + // the last tile finishes so callers can sequence work afterward. + function revealRow( + rowTiles: HTMLElement[], + guess: string, + states: TileState[], + ): Promise<void> { + return new Promise((resolve) => { + rowTiles.forEach((tile, i) => { + const delay = i * STAGGER_MS; + // Start flip + setTimeout(() => { + tile.classList.add('wordle-board__tile--flipping'); + // At the halfway point, swap in the color + setTimeout(() => { + tile.className = `wordle-board__tile wordle-board__tile--${states[i]} wordle-board__tile--flipping`; + }, FLIP_MS / 2); + // When animation ends, remove the flipping class + tile.addEventListener('animationend', () => { + tile.classList.remove('wordle-board__tile--flipping'); + if (i === rowTiles.length - 1) resolve(); + }, { once: true }); + }, delay); + }); + }); + } + function submitGuess(): void { if (currentInput.length !== WORD_LEN) { flash('Not enough letters'); @@ -157,37 +188,38 @@ export function mountWordleBoard(container: HTMLElement, props: WordleBoardProps } const states = evaluate(guess, secret); + const row = tiles[currentRow]; - // Color tiles - tiles[currentRow].forEach((tile, i) => { - tile.textContent = guess[i]; - tile.className = `wordle-board__tile wordle-board__tile--${states[i]}`; - }); - - // Merge letter states for keyboard - states.forEach((s, i) => { - if (s === 'correct' || s === 'present' || s === 'absent') { - letterStates[guess[i]] = mergeLetterState(letterStates[guess[i]], s); - } - }); - updateKeyboard(keyboardEl, letterStates); - + // Lock input during animation + done = true; + currentInput = ''; guesses.push(guess); currentRow++; - currentInput = ''; const solved = states.every((s) => s === 'correct'); - if (solved) { - done = true; - giveUpBtn.hidden = true; - message.textContent = PRAISE[Math.min(guesses.length - 1, PRAISE.length - 1)]; - scheduleRoundEnd(true); - } else if (currentRow >= MAX_GUESSES) { - done = true; - giveUpBtn.hidden = true; - message.textContent = `The word was ${secret}`; - scheduleRoundEnd(false); - } + + revealRow(row, guess, states).then(() => { + // Merge letter states for keyboard only after all tiles have flipped + states.forEach((s, i) => { + if (s === 'correct' || s === 'present' || s === 'absent') { + letterStates[guess[i]] = mergeLetterState(letterStates[guess[i]], s); + } + }); + updateKeyboard(keyboardEl, letterStates); + + if (solved) { + giveUpBtn.hidden = true; + message.textContent = PRAISE[Math.min(guesses.length - 1, PRAISE.length - 1)]; + scheduleRoundEnd(true); + } else if (currentRow >= MAX_GUESSES) { + giveUpBtn.hidden = true; + message.textContent = `The word was ${secret}`; + scheduleRoundEnd(false); + } else { + // Allow input again for the next row + done = false; + } + }); } function flash(text: string): void { diff --git a/src/index.html b/src/index.html index 342cbba..807df7a 100644 --- a/src/index.html +++ b/src/index.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> <title>Word Waltz</title> <link rel="icon" type="image/x-icon" href="/favicon.ico" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> diff --git a/src/styles/main.css b/src/styles/main.css index e1fd9ff..f5e3aac 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -10,8 +10,8 @@ --color-text: #1a1a1a; --color-muted: #6b6b6b; - --color-correct: #538d4e; - --color-present: #b59f3b; + --color-correct: #6aaa64; + --color-present: #e5a428; --color-absent: #787c7e; --color-tile-bg: #ffffff; --color-tile-text: #1a1a1a; @@ -435,14 +435,39 @@ header { font-family: var(--font-mono); background: var(--color-tile-bg); color: var(--color-tile-text); - transition: border-color 0.1s; user-select: none; + /* flip animation */ + perspective: 250px; + transition: border-color 0.1s; } .wordle-board__tile--filled { border-color: #999; } +/* Flip keyframe: rotate around X, colour applied at 50% */ +@keyframes tile-flip { + 0% { + transform: rotateX(0deg); + } + + 49% { + transform: rotateX(-90deg); + } + + 50% { + transform: rotateX(-90deg); + } + + 100% { + transform: rotateX(0deg); + } +} + +.wordle-board__tile--flipping { + animation: tile-flip 0.5s ease-in-out forwards; +} + .wordle-board__tile--correct { background: var(--color-correct); border-color: var(--color-correct); @@ -493,6 +518,7 @@ header { align-items: center; gap: 8px; width: 100%; + padding-bottom: env(safe-area-inset-bottom, 0px); --key-w: clamp(26px, calc(10vw - 6.9px), 43px); --key-w-wide: clamp(40px, calc(15vw - 10px), 65px); }