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:
TehRiehlDeal committed 2026-09-12 22:42:55 -07:00
1 parent da32e49b0b
commit 8d0b7f39ee
122 files changed
+3129 -2089

No files matched your search

+13 -13
View File
@@ -65,7 +65,7 @@ describe('ShareableResult', () => {
render(<ShareableResult {...baseProps()} />);
await clickAndSettle(
screen.getByRole('button', { name: /Copy Result|Copied!/ }),
screen.getByRole('button', { name: /Copy result|Copied/ }),
);
expect(writeText).toHaveBeenCalledTimes(1);
@@ -80,17 +80,17 @@ describe('ShareableResult', () => {
render(<ShareableResult {...baseProps()} />);
await clickAndSettle(
screen.getByRole('button', { name: /Copy Result|Copied!/ }),
screen.getByRole('button', { name: /Copy result|Copied/ }),
);
expect(
screen.getByRole('button', { name: /Copied!/ }),
screen.getByRole('button', { name: /^Copied$/ }),
).toBeInTheDocument();
await act(async () => {
vi.advanceTimersByTime(2000);
});
expect(
screen.getByRole('button', { name: /Copy Result/ }),
screen.getByRole('button', { name: /Copy result/ }),
).toBeInTheDocument();
});
@@ -99,9 +99,9 @@ describe('ShareableResult', () => {
<ShareableResult {...baseProps()} mode={{ type: 'daily', date: '2024-01-01' }} />,
);
await clickAndSettle(
screen.getByRole('button', { name: /Copy Result|Copied!/ }),
screen.getByRole('button', { name: /Copy result|Copied/ }),
);
expect(writeText.mock.calls[0]?.[0]).toContain('Daily Challenge');
expect(writeText.mock.calls[0]?.[0]).toContain('Daily challenge');
expect(writeText.mock.calls[0]?.[0]).toContain('/daily');
rerender(
@@ -111,16 +111,16 @@ describe('ShareableResult', () => {
/>,
);
await clickAndSettle(
screen.getByRole('button', { name: /Copy Result|Copied!/ }),
screen.getByRole('button', { name: /Copy result|Copied/ }),
);
expect(writeText.mock.calls[1]?.[0]).toContain('Async Versus');
expect(writeText.mock.calls[1]?.[0]).toContain('Versus');
expect(writeText.mock.calls[1]?.[0]).toContain('/versus/m1');
});
it('links to plain versus play when no match id is given', async () => {
render(<ShareableResult {...baseProps()} mode={{ type: 'versus' }} />);
await clickAndSettle(
screen.getByRole('button', { name: /Copy Result|Copied!/ }),
screen.getByRole('button', { name: /Copy result|Copied/ }),
);
expect(writeText.mock.calls[0]?.[0]).toMatch(/\/versus$/);
});
@@ -135,7 +135,7 @@ describe('ShareableResult', () => {
/>,
);
await clickAndSettle(
screen.getByRole('button', { name: /Copy Result|Copied!/ }),
screen.getByRole('button', { name: /Copy result|Copied/ }),
);
const text = writeText.mock.calls[0]?.[0] as string;
expect(text).toContain('Movie A <-> Movie B');
@@ -147,11 +147,11 @@ describe('ShareableResult', () => {
render(<ShareableResult {...baseProps()} />);
await clickAndSettle(
screen.getByRole('button', { name: /Copy Result|Copied!/ }),
screen.getByRole('button', { name: /Copy result|Copied/ }),
);
expect(
screen.getByRole('button', { name: /Copy Result/ }),
screen.getByRole('button', { name: /Copy result/ }),
).toBeInTheDocument();
});
@@ -160,7 +160,7 @@ describe('ShareableResult', () => {
const { unmount } = render(<ShareableResult {...baseProps()} />);
await clickAndSettle(
screen.getByRole('button', { name: /Copy Result|Copied!/ }),
screen.getByRole('button', { name: /Copy result|Copied/ }),
);
unmount();