AT Mot — a bilingual (EN/FR) daily word game native to the AT Protocol.
0

Configure Feed

Select the types of activity you want to include in your feed.

Stagger the row flip-reveal per tile and hide colour until mid-flip

+57 -7
+17 -2
src/ui/board.ts
··· 21 21 * Every other render (keystrokes, restored games, language switches) passes the 22 22 * default `-1`, so already-revealed rows keep their colours without re-flipping 23 23 * each time the board is rebuilt. 24 + * 25 + * On the animated row each tile's flip is delayed by its column index (see 26 + * `REVEAL_STEP_MS`), so the result is revealed letter by letter from left to 27 + * right; the colour stays hidden until the flip's midpoint (handled in CSS). 24 28 */ 29 + /** Delay between consecutive tile flips on a freshly-submitted row, in ms. */ 30 + const REVEAL_STEP_MS = 300; 31 + 25 32 export function renderBoard(state: GameState, pending: string, animateRow = -1): HTMLElement { 26 33 const rows = scoredRows(state); 27 34 const board = el('div', { class: 'board', role: 'grid', ariaLabel: t(state.lang).play }); ··· 35 42 let letter = ''; 36 43 const cls = ['tile']; 37 44 let label: string | undefined; 45 + let style: string | undefined; 38 46 39 47 if (guess && states) { 40 48 letter = guess[c] ?? ''; 41 49 const s = states[c]!; 42 50 cls.push(s); 43 - if (r === animateRow) cls.push('reveal'); 51 + if (r === animateRow) { 52 + cls.push('reveal'); 53 + style = `--reveal-delay: ${c * REVEAL_STEP_MS}ms`; 54 + } 44 55 label = `${letter}, ${STATE_LABEL[s][state.lang]}`; 45 56 } else if (r === activeRow) { 46 57 letter = pending[c] ?? ''; ··· 48 59 } 49 60 50 61 row.append( 51 - el('div', { class: cls.join(' '), role: 'gridcell', ariaLabel: label ?? letter }, letter), 62 + el( 63 + 'div', 64 + { class: cls.join(' '), role: 'gridcell', ariaLabel: label ?? letter, style }, 65 + letter, 66 + ), 52 67 ); 53 68 } 54 69 board.append(row);
+32 -5
src/ui/theme.css
··· 290 290 .tile.absent { 291 291 color: var(--tile-fg); 292 292 border-color: transparent; 293 + /* --reveal-bg / --reveal-ring carry the destination colour so the flip 294 + keyframe can apply it at the midpoint; static rows just use them directly. */ 295 + background: var(--reveal-bg); 296 + box-shadow: var(--reveal-ring, none); 293 297 } 294 298 .tile.correct { 295 - background: var(--tile-correct); 299 + --reveal-bg: var(--tile-correct); 296 300 } 297 301 .tile.present { 298 - background: var(--tile-present); 302 + --reveal-bg: var(--tile-present); 299 303 /* secondary non-hue cue: inner ring distinguishes present from correct */ 300 - box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.45); 304 + --reveal-ring: inset 0 0 0 2px rgba(255, 255, 255, 0.45); 301 305 } 302 306 .tile.absent { 303 - background: var(--tile-absent); 307 + --reveal-bg: var(--tile-absent); 304 308 } 309 + /* Freshly-submitted row: flip each tile in turn (delay set per tile in board.ts) 310 + and keep the colour hidden until the tile is edge-on at the flip's midpoint. */ 305 311 .tile.reveal { 306 - animation: flip 0.5s ease forwards; 312 + animation: flip 0.5s ease both; 313 + animation-delay: var(--reveal-delay, 0ms); 307 314 } 308 315 309 316 @keyframes pop { ··· 317 324 @keyframes flip { 318 325 0% { 319 326 transform: rotateX(0); 327 + background: transparent; 328 + border-color: var(--tile-edge); 329 + color: var(--fg); 330 + box-shadow: none; 320 331 } 321 332 50% { 322 333 transform: rotateX(90deg); 334 + background: transparent; 335 + border-color: var(--tile-edge); 336 + color: var(--fg); 337 + box-shadow: none; 338 + } 339 + 50.01% { 340 + background: var(--reveal-bg); 341 + border-color: transparent; 342 + color: var(--tile-fg); 343 + box-shadow: var(--reveal-ring, none); 323 344 } 324 345 100% { 325 346 transform: rotateX(0); 347 + background: var(--reveal-bg); 348 + border-color: transparent; 349 + color: var(--tile-fg); 350 + box-shadow: var(--reveal-ring, none); 326 351 } 327 352 } 328 353 .row-shake { ··· 611 636 *::before, 612 637 *::after { 613 638 animation-duration: 0.001ms !important; 639 + /* zero the per-tile flip stagger too, so colours appear at once */ 640 + animation-delay: 0ms !important; 614 641 transition-duration: 0.001ms !important; 615 642 } 616 643 }
+8
tests/board.test.ts
··· 32 32 const firstRow = board.querySelectorAll('.board-row')[0]!; 33 33 animated.forEach((tile) => expect(firstRow.contains(tile)).toBe(true)); 34 34 }); 35 + 36 + it('staggers the reveal so tiles flip one by one left to right', () => { 37 + const board = renderBoard(gameWithOneGuess(), '', 0); 38 + const delays = Array.from(board.querySelectorAll<HTMLElement>('.tile.reveal')).map((tile) => 39 + tile.style.getPropertyValue('--reveal-delay'), 40 + ); 41 + expect(delays).toEqual(['0ms', '300ms', '600ms', '900ms', '1200ms']); 42 + }); 35 43 });