feat(ui): give the app one visual identity as Movie Loop
Gold is the primary in both themes, Bricolage Grotesque carries the wordmark, titles and the score, and Movie A / Movie B wear the blue and magenta of three-strip Technicolor on their posters instead of colouring whole rows. The loop closes on screen - connectors light, a gold line draws back to Movie A, the row glows, then the result arrives - in place of confetti, on every play mode, scaled to the chain's length. Every page shares one header and hairline lists; copy is sentence case with one verb per flow, and the Home hero shows a real finished loop. Also clamps link counts at zero: an opponent who had not moved yet showed "-1 links".
This commit is contained in:
1 parent
da32e49b0b
commit
8d0b7f39ee
122 files changed
+3129
-2089
No files matched your search
+14
-14
@@ -91,14 +91,14 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('filters panel', () => {
|
||||
it('is hidden until "Show Filters" is toggled', async () => {
|
||||
it('is hidden until "Show filters" is toggled', async () => {
|
||||
renderPage();
|
||||
expect(screen.queryByText('Genre')).toBeNull();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Show Filters' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Show filters' }));
|
||||
expect(screen.getByText('Genre')).toBeInTheDocument();
|
||||
expect(screen.getByText('Decade')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Hide Filters' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Hide filters' }));
|
||||
expect(screen.queryByText('Genre')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -107,10 +107,10 @@ describe('starting a game', () => {
|
||||
it('uses a preset pair for the chosen decade', async () => {
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0);
|
||||
renderPage();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Show Filters' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Show filters' }));
|
||||
await userEvent.click(screen.getByText('1970s'));
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Start Practice Game' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Play' }));
|
||||
|
||||
expect(await screen.findByText('Game Page')).toBeInTheDocument();
|
||||
expect(discoverSpy).not.toHaveBeenCalled();
|
||||
@@ -122,14 +122,14 @@ describe('starting a game', () => {
|
||||
|
||||
it('falls back to a TMDB discover pair once a filter is reset to "Any"', async () => {
|
||||
renderPage();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Show Filters' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Show filters' }));
|
||||
await userEvent.click(screen.getByText('Comedy'));
|
||||
await userEvent.click(screen.getByText('1970s'));
|
||||
// Reset both filters back to unfiltered before starting.
|
||||
await userEvent.click(screen.getAllByText('Any')[0] as HTMLElement);
|
||||
await userEvent.click(screen.getAllByText('Any')[1] as HTMLElement);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Start Practice Game' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Play' }));
|
||||
|
||||
expect(await screen.findByText('Game Page')).toBeInTheDocument();
|
||||
expect(discoverSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -144,11 +144,11 @@ describe('starting a game', () => {
|
||||
);
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0);
|
||||
renderPage();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Show Filters' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Show filters' }));
|
||||
await userEvent.click(screen.getByText('1970s'));
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Start Practice Game' }));
|
||||
const loadingButton = await screen.findByRole('button', { name: 'Loading...' });
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Play' }));
|
||||
const loadingButton = await screen.findByRole('button', { name: 'Finding a pair…' });
|
||||
expect(loadingButton).toBeDisabled();
|
||||
|
||||
release();
|
||||
@@ -162,11 +162,11 @@ describe('starting a game', () => {
|
||||
discoverSpy.mockRejectedValue(new Error('Network Error'));
|
||||
renderPage();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Start Practice Game' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Play' }));
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(/Network error/);
|
||||
expect(screen.queryByText('Game Page')).toBeNull();
|
||||
expect(screen.getByRole('button', { name: 'Start Practice Game' })).toBeEnabled();
|
||||
expect(screen.getByRole('button', { name: 'Play' })).toBeEnabled();
|
||||
});
|
||||
|
||||
it('explains a page with too few usable movies rather than failing silently', async () => {
|
||||
@@ -178,7 +178,7 @@ describe('starting a game', () => {
|
||||
});
|
||||
renderPage();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Start Practice Game' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Play' }));
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(/not return enough movies/);
|
||||
});
|
||||
@@ -187,7 +187,7 @@ describe('starting a game', () => {
|
||||
movieDetailsSpy.mockRejectedValue(new Error('network'));
|
||||
renderPage();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Start Practice Game' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Play' }));
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(
|
||||
'Failed to load movie details. Please try again.',
|
||||
|
||||
Reference in new issue
Block a user