Commit Graph
4 Commits
Author SHA1 Message Date
TehRiehlDeal 8d0b7f39ee 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".
2026-09-12 22:42:55 -07:00
TehRiehlDeal 889cdda83f feat(a11y): make the app usable from a keyboard and a screen reader
eslint-plugin-jsx-a11y joins the lint step and found 19 violations on
arrival - 15 orphaned labels, a clickable div, a combobox missing its
required attributes, an autoFocus. All fixed, none suppressed, and the
rule set stays enforcing so the next one fails CI.

The two hand-rolled overlays were the worst of it. The notification bell
was a div with div-onClick rows: nothing focusable, nothing announced, no
way to close it from the keyboard. It is a real popover now - the
trigger reports its state and the unread count, the panel is a labelled
dialog, every row is a button with its actions beside it rather than
nested inside it, Escape closes and returns focus. The Challenge Friend
overlay had no role, no focus trap and no Escape, so Tab went straight
through it into the page behind; it is the UI kit's Dialog now, which
supplies all of that.

Every form control has a label, including the three change-password
fields that had only placeholders and a game-night join-password field
that had nothing at all. Every icon-only button says what it does,
including three versus buttons whose content collapsed to a bare spinner
while loading. The search combobox follows the ARIA 1.2 pattern, with
the role on the input; verified live that aria-expanded flips and
aria-controls resolves to the listbox.

State that changed silently is announced: link validation, both lobby
countdowns (assertively - a 3-2-1 that launches the game is not
something to miss), the game-night player list. The game header's
"Passed Movie B" read identically in both states and only the icon
differed, so a screen reader heard the wrong thing until B was reached.

A skip-to-content link on every page, and a global reduced-motion rule
that collapses every animation when the OS asks; the confetti overlay
does not render at all under it.

Addresses audit section 2.8.
2026-09-11 18:45:06 -07:00
TehRiehlDeal 470a67607b fix(lint): resolve every eslint warning without suppressions
react-hooks/set-state-in-effect and react-hooks/purity had been demoted
to warnings "so CI passes", and CI ran without --max-warnings 0, so all
22 violations were invisible there. Both rules are back at error.

- Confetti is rolled once per mount instead of during render, where it
  re-rolled on every re-render and made pieces visibly jump.
- Loading flags are derived from a "last settled request" key rather than
  set synchronously inside an effect. That also adds the cancellation
  these fetches were missing, so a resolved request can no longer write
  into a unmounted or superseded view.
- Effect dependency arrays corrected; the two eslint-disable-line
  comments in Profile.tsx are gone, and the split of its two effects
  removes the guard that silently never re-ran.
- GameCompletionModal derives visibility from status instead of mirroring
  it into state, with a ref so the sound and localStorage write fire once.
- ValidationFeedback adjusts state during render, the documented pattern,
  instead of paying an extra committed render per link.
- The three react-refresh suppressions in ui/ are resolved properly by
  extracting the variants into their own modules, so the component files
  export only components. badgeVariants had no consumers and is no longer
  exported.
- Drops a stale eslint-disable directive in sw.js.

The lint script now fails on warnings so this cannot quietly regress.
2026-09-09 19:59:10 -07:00
TehRiehlDeal 0ceb2fac12 Initial commit 2026-03-09 15:03:08 -07:00