diff --git a/apps/client/src/components/RoomLobby.test.tsx b/apps/client/src/components/RoomLobby.test.tsx index 66b5d88..49febc6 100644 --- a/apps/client/src/components/RoomLobby.test.tsx +++ b/apps/client/src/components/RoomLobby.test.tsx @@ -225,7 +225,8 @@ describe('RoomLobby word entry', () => { const { container } = renderLobby(); const preview = container.querySelector('.room-dashboard__preview'); - expect(preview?.firstElementChild).toHaveClass('board-panel'); + expect(preview?.firstElementChild).toHaveClass('panel', 'board-panel'); + expect(container.querySelector('.board-panel--active')).toBeNull(); const puzzle = screen.getByRole('region', { name: 'Puzzle' }); const modePanel = screen.getByRole('group', { name: 'Word entry mode' }); expect(puzzle).toBeVisible(); @@ -415,6 +416,11 @@ describe('RoomLobby word entry', () => { const button = screen.getByRole('button', { name: 'Return to Lobby' }); expect(button).toHaveClass('button', 'button--primary'); + expect( + screen + .getByRole('region', { name: 'Round summary' }) + .closest('.board-panel'), + ).toHaveClass('panel', 'board-panel'); expect( screen .getByRole('region', { name: 'Round summary' }) @@ -673,6 +679,7 @@ describe('RoomLobby word entry', () => { const startRound = screen.getByRole('button', { name: 'Start Round' }); expect(startRound).toBeVisible(); + expect(puzzle).toHaveClass('panel', 'board-panel'); expect(startRound.closest('.round-action')).not.toBeNull(); expect(startRound.closest('.round-action')).not.toBe(puzzle); expect(puzzle).not.toContainElement(settings); @@ -793,6 +800,16 @@ describe('RoomLobby word entry', () => { expect( screen.queryByRole('region', { name: 'Game settings' }), ).toBeNull(); + expect( + screen.getByRole('region', { + name: phase === 'ROUND_ENDED' ? 'Round summary' : 'Puzzle', + }), + ).toHaveClass('panel', 'board-panel'); + expect( + screen.queryByRole('button', { + name: /Start Round|Return to Lobby/, + }), + ).toBeNull(); view.unmount(); } diff --git a/apps/client/src/components/RoundResults.test.tsx b/apps/client/src/components/RoundResults.test.tsx index a77482c..e61d168 100644 --- a/apps/client/src/components/RoundResults.test.tsx +++ b/apps/client/src/components/RoundResults.test.tsx @@ -51,7 +51,7 @@ describe('RoundResults', () => { ).not.toHaveClass('result-player-card--winner'); }); - it.each([1, 2, 3, 4, 6, 8])( + it.each([1, 2, 3, 4, 5, 6, 7, 8])( 'keeps %i result cards in an explicit intrinsic-width layout variant', (count) => { render(); diff --git a/apps/client/src/styles.css b/apps/client/src/styles.css index 41ffea8..9ceaa5c 100644 --- a/apps/client/src/styles.css +++ b/apps/client/src/styles.css @@ -274,10 +274,12 @@ button:disabled { .display-results__cards--1 { grid-template-columns: minmax(16rem, max-content); } -.display-results__cards--5, -.display-results__cards--6 { +.display-results__cards--3 { grid-template-columns: repeat(3, minmax(16rem, max-content)); } +.display-results__cards--4, +.display-results__cards--5, +.display-results__cards--6, .display-results__cards--7, .display-results__cards--8 { grid-template-columns: repeat(4, minmax(16rem, max-content)); @@ -1624,7 +1626,7 @@ button:disabled { align-items: stretch; flex-direction: column; gap: 1rem; - margin-block: 1rem; + margin: 1rem 0 0; padding: 0.75rem 0.9rem; border: 1px solid var(--line); border-radius: var(--radius-md); @@ -2030,6 +2032,15 @@ button:disabled { gap: 0.65rem; } +.room-dashboard--phone .round-action { + justify-content: center; + margin-top: 0; +} + +.room-dashboard--phone .round-action .button { + width: 100%; +} + .room-dashboard--phone .board-panel { padding: 0.75rem; } @@ -2108,6 +2119,48 @@ button:disabled { width: min(100%, 26rem); } +@media (min-width: 62rem) { + .room-dashboard--phone .board-panel, + .room-dashboard--phone .round-action, + .room-dashboard--phone .settings-panel, + .room-dashboard--phone .controller-panel { + width: min(100%, 27.5rem); + justify-self: center; + } +} + +@media (orientation: portrait) { + .display-room-page .display-room-layout { + grid-template-columns: minmax(0, 1fr); + align-items: start; + justify-items: center; + } + + .display-room-layout > .display-puzzle-panel { + width: min(100%, 44rem); + order: 1; + } + + .display-room-layout > .display-side-panel { + order: 2; + } + + .display-room-layout > .display-side-stack { + order: 3; + } + + .display-results__cards, + .display-results__cards--1, + .display-results__cards--3, + .display-results__cards--4, + .display-results__cards--5, + .display-results__cards--6, + .display-results__cards--7, + .display-results__cards--8 { + grid-template-columns: minmax(16rem, min(24rem, 90vw)); + } +} + @media (max-width: 430px) and (max-height: 760px) { .room-page--phone .letter-grid { --letter-tile-font-size: clamp(1.15rem, 7.8vw, 1.8rem); diff --git a/apps/client/src/styles.test.ts b/apps/client/src/styles.test.ts index 8ab393c..0052744 100644 --- a/apps/client/src/styles.test.ts +++ b/apps/client/src/styles.test.ts @@ -73,6 +73,31 @@ describe('tile typography', () => { /\.choice-group \+ \.choice-group\s*\{[^}]*margin-top: 0\.8rem;/s, ); expect(styles).toMatch(/\.word-entry__actions\s*\{[^}]*margin-top: 0;/s); + expect(styles).toMatch(/\.word-entry\s*\{[^}]*margin: 1rem 0 0;/s); + expect(styles).toMatch( + /\.room-dashboard--phone \.round-action\s*\{[^}]*justify-content: center;[^}]*margin-top: 0;/s, + ); + expect(styles).toMatch( + /\.room-dashboard--phone \.round-action \.button\s*\{[^}]*width: 100%;/s, + ); + expect(styles).toMatch( + /\.room-dashboard--phone \.room-dashboard__preview\s*\{[^}]*gap: 0\.65rem;/s, + ); + expect(styles).toMatch( + /\.room-dashboard--phone \.letter-grid\s*\{[^}]*width: min\(100%, 26rem\);/s, + ); + expect(styles).toMatch( + /@media \(min-width: 62rem\)[\s\S]*?\.room-dashboard--phone \.board-panel,\s*\.room-dashboard--phone \.round-action,\s*\.room-dashboard--phone \.settings-panel,\s*\.room-dashboard--phone \.controller-panel\s*\{[^}]*width: min\(100%, 27\.5rem\);[^}]*justify-self: center;/s, + ); + const basePlayerStyles = styles.slice( + styles.indexOf('.room-dashboard--phone {'), + styles.indexOf('@media (min-width: 62rem)'), + ); + expect(basePlayerStyles).not.toContain('27.5rem'); + expect(styles).not.toContain('.board-panel--active'); + expect(styles).toMatch( + /@media \(max-width: 44rem\)[\s\S]*?\.round-action \.button\s*\{[^}]*width: 100%;/s, + ); }); it('keeps the display presentation centered and within TV-height bounds', () => { @@ -149,7 +174,10 @@ describe('tile typography', () => { /\.display-results__cards--1\s*\{[^}]*grid-template-columns: minmax\(16rem, max-content\);/s, ); expect(styles).toMatch( - /\.display-results__cards--7,\s*\.display-results__cards--8\s*\{[^}]*repeat\(4, minmax\(16rem, max-content\)\);/s, + /\.display-results__cards--3\s*\{[^}]*repeat\(3, minmax\(16rem, max-content\)\);/s, + ); + expect(styles).toMatch( + /\.display-results__cards--4,\s*\.display-results__cards--5,\s*\.display-results__cards--6,\s*\.display-results__cards--7,\s*\.display-results__cards--8\s*\{[^}]*repeat\(4, minmax\(16rem, max-content\)\);/s, ); expect(styles).not.toMatch(/\.display-results__cards\s*\{[^}]*\b1fr\b/s); expect(styles).toMatch( @@ -216,6 +244,30 @@ describe('tile typography', () => { ); }); + it('stacks the display puzzle and side panels safely in portrait orientation', () => { + const portraitDisplayStyles = styles.match( + /@media \(orientation: portrait\) \{([\s\S]*?)\n\}\n\n@media \(max-width: 430px\)/, + )?.[1]; + + expect(portraitDisplayStyles).toBeDefined(); + expect(portraitDisplayStyles).toMatch( + /\.display-room-page \.display-room-layout\s*\{[^}]*grid-template-columns: minmax\(0, 1fr\);[^}]*align-items: start;[^}]*justify-items: center;/s, + ); + expect(portraitDisplayStyles).toMatch( + /\.display-room-layout > \.display-puzzle-panel\s*\{[^}]*width: min\(100%, 44rem\);[^}]*order: 1;/s, + ); + expect(portraitDisplayStyles).toMatch( + /\.display-room-layout > \.display-side-panel\s*\{[^}]*order: 2;/s, + ); + expect(portraitDisplayStyles).toMatch( + /\.display-room-layout > \.display-side-stack\s*\{[^}]*order: 3;/s, + ); + expect(portraitDisplayStyles).toMatch( + /\.display-results__cards,[^}]*grid-template-columns: minmax\(16rem, min\(24rem, 90vw\)\);/s, + ); + expect(portraitDisplayStyles).not.toContain('55vw'); + }); + it('keeps the mid-round phone waiting notice deliberately spaced below the puzzle', () => { expect(styles).toMatch( /\.room-dashboard--phone \.board-panel \.letter-grid \+ \.prototype-notice\s*\{[^}]*margin-top: 0\.75rem;/s,