From c4e360afd89548b36a9721c04ea5980af88c2ae1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Apr 2026 01:15:29 +0000
Subject: [PATCH 1/4] Initial plan
From a89cff75eb54abfd8e37a4aa64187c78492f7b06 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Apr 2026 01:22:11 +0000
Subject: [PATCH 2/4] feat: make game cards entirely clickable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Move the game:select event handler from the inner Play button to the
article element so that clicking anywhere on a game card (thumbnail,
title, description, score line, or button) starts the game.
CSS changes:
- Add `cursor: pointer` to `.game-card` for clear affordance
- Add `.game-card:focus-within` outline so the whole card highlights
when the Play button inside receives keyboard focus
- Restore `.game-card button:focus-visible` outline (WCAG AA safety net)
JS changes (gameCard.js):
- Replace `button.addEventListener('click', ...)` with
`article.addEventListener('click', ...)` — button clicks bubble up
naturally, so keyboard Enter/Space still works without extra wiring
Test changes (gameCard.test.js):
- Add "clicking the card body fires game:select" test
Agent-Logs-Url: https://github.com/acrosman/BrainSpeedExercises/sessions/3757ccae-218a-4cd5-91a7-b32923280193
Co-authored-by: acrosman <2972053+acrosman@users.noreply.github.com>
---
app/components/gameCard.js | 8 +++++---
app/components/tests/gameCard.test.js | 16 ++++++++++++++++
app/styles/game-card.css | 7 +++++++
3 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/app/components/gameCard.js b/app/components/gameCard.js
index cee2736..bd716c5 100644
--- a/app/components/gameCard.js
+++ b/app/components/gameCard.js
@@ -72,16 +72,18 @@ export function createGameCard(manifest, progress) {
button.setAttribute('aria-label', `Play ${manifest.name}`);
/**
- * Dispatches a custom event when the game card button is clicked.
+ * Dispatches a game:select custom event when any part of the card is clicked.
+ * The button inside the card bubbles its click up to the article, so both
+ * direct card clicks and button activations (keyboard Enter/Space) are handled here.
* @fires CustomEvent#game:select
*/
- button.addEventListener('click', () => {
+ article.addEventListener('click', () => {
const event = new CustomEvent('game:select', {
bubbles: true,
composed: true,
detail: { gameId: manifest.id },
});
- button.dispatchEvent(event);
+ article.dispatchEvent(event);
});
article.appendChild(img);
diff --git a/app/components/tests/gameCard.test.js b/app/components/tests/gameCard.test.js
index 695557f..ad0d080 100644
--- a/app/components/tests/gameCard.test.js
+++ b/app/components/tests/gameCard.test.js
@@ -64,6 +64,22 @@ describe('createGameCard', () => {
expect(firedEvent.detail.gameId).toBe(validManifest.id);
});
+ it('clicking the card body (outside the button) fires game:select', () => {
+ const card = createGameCard(validManifest);
+ document.body.appendChild(card);
+
+ let firedEvent = null;
+ card.addEventListener('game:select', (e) => {
+ firedEvent = e;
+ });
+
+ // Simulate a click directly on the article element (not via the button).
+ card.click();
+
+ expect(firedEvent).not.toBeNull();
+ expect(firedEvent.detail.gameId).toBe(validManifest.id);
+ });
+
it('
has a non-empty alt attribute', () => {
const card = createGameCard(validManifest);
const img = card.querySelector('img');
diff --git a/app/styles/game-card.css b/app/styles/game-card.css
index dbea124..23a54be 100644
--- a/app/styles/game-card.css
+++ b/app/styles/game-card.css
@@ -16,12 +16,19 @@
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
transition: box-shadow var(--transition-fast);
+ cursor: pointer;
}
.game-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
+/* Show a card-level focus ring when the Play button inside receives keyboard focus. */
+.game-card:focus-within {
+ outline: 3px solid var(--focus-ring);
+ outline-offset: 2px;
+}
+
.game-card img {
width: 100%;
aspect-ratio: 1 / 1;
From c2f400be4159979c736b17b33ce4c3a41201774d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Apr 2026 01:37:37 +0000
Subject: [PATCH 3/4] refactor: move bubbling note inline, trim JSDoc in
gameCard.js
Agent-Logs-Url: https://github.com/acrosman/BrainSpeedExercises/sessions/de4eb621-6b1e-4ce6-b1c0-203df72e7b61
Co-authored-by: acrosman <2972053+acrosman@users.noreply.github.com>
---
app/components/gameCard.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/components/gameCard.js b/app/components/gameCard.js
index bd716c5..3077b3e 100644
--- a/app/components/gameCard.js
+++ b/app/components/gameCard.js
@@ -73,10 +73,10 @@ export function createGameCard(manifest, progress) {
/**
* Dispatches a game:select custom event when any part of the card is clicked.
- * The button inside the card bubbles its click up to the article, so both
- * direct card clicks and button activations (keyboard Enter/Space) are handled here.
* @fires CustomEvent#game:select
*/
+ // Button clicks bubble up to the article, so keyboard Enter/Space activations
+ // on the Play button are also handled here without additional wiring.
article.addEventListener('click', () => {
const event = new CustomEvent('game:select', {
bubbles: true,
From e15c0b89099b24a756a81318a415c6fd75261926 Mon Sep 17 00:00:00 2001
From: Aaron Crosman
Date: Tue, 14 Apr 2026 21:40:23 -0400
Subject: [PATCH 4/4] Removed useless comment.
---
app/components/gameCard.js | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/app/components/gameCard.js b/app/components/gameCard.js
index 3077b3e..1e0ffd6 100644
--- a/app/components/gameCard.js
+++ b/app/components/gameCard.js
@@ -55,7 +55,7 @@ export function createGameCard(manifest, progress) {
// Show time played today if available.
const today = getTodayDateString();
if (progress.dailyTime && typeof progress.dailyTime[today] === 'number'
- && progress.dailyTime[today] > 0) {
+ && progress.dailyTime[today] > 0) {
details.push(`Today: ${formatDuration(progress.dailyTime[today])}`);
}
if (details.length > 0) {
@@ -71,12 +71,8 @@ export function createGameCard(manifest, progress) {
button.textContent = `Play ${manifest.name}`;
button.setAttribute('aria-label', `Play ${manifest.name}`);
- /**
- * Dispatches a game:select custom event when any part of the card is clicked.
- * @fires CustomEvent#game:select
- */
- // Button clicks bubble up to the article, so keyboard Enter/Space activations
- // on the Play button are also handled here without additional wiring.
+
+ // Dispatches a game:select custom event when any part of the card is clicked.
article.addEventListener('click', () => {
const event = new CustomEvent('game:select', {
bubbles: true,