diff --git a/app.js b/app.js
index 95a98d9..15093c7 100644
--- a/app.js
+++ b/app.js
@@ -116,6 +116,7 @@ let currentPage = 1;
let currentPageGameIds = [];
let currentTotalPages = 1;
let currentView = "catalogue";
+let pendingFocusGameId = null;
// V2 is now the default UI. Use ?ui=v1 to force legacy mode if needed.
const uiParam = new URLSearchParams(window.location.search).get("ui");
const uiV2Enabled = uiParam !== "v1";
@@ -256,6 +257,40 @@ quickSearchInput.addEventListener("input", (event) => {
renderSearchResults();
});
+quickSearchResults.addEventListener("click", (event) => {
+ if (!(event.target instanceof Element)) {
+ return;
+ }
+ const target = event.target.closest(".search-hit[data-game-id]");
+ if (!(target instanceof HTMLElement)) {
+ return;
+ }
+ const gameId = normalizeText(target.dataset.gameId);
+ if (!gameId) {
+ return;
+ }
+ openGameFromSearchResult(gameId);
+});
+
+quickSearchResults.addEventListener("keydown", (event) => {
+ if (!(event.target instanceof Element)) {
+ return;
+ }
+ if (event.key !== "Enter" && event.key !== " ") {
+ return;
+ }
+ const target = event.target.closest(".search-hit[data-game-id]");
+ if (!(target instanceof HTMLElement)) {
+ return;
+ }
+ event.preventDefault();
+ const gameId = normalizeText(target.dataset.gameId);
+ if (!gameId) {
+ return;
+ }
+ openGameFromSearchResult(gameId);
+});
+
loanedFilterBtn.addEventListener("click", () => {
setCurrentView(currentView === "loans" ? "catalogue" : "loans");
});
@@ -1381,7 +1416,7 @@ function renderSearchResults() {
]
.filter(Boolean)
.join(" | ");
- return `${escapeHtml(game.title || "")}${escapeHtml(meta)}
`;
+ return `${escapeHtml(game.title || "")}${escapeHtml(meta)}
`;
})
.join("");
@@ -1393,6 +1428,37 @@ function renderSearchResults() {
quickSearchResults.innerHTML = `${header}${items}${more}`;
}
+function openGameFromSearchResult(gameId) {
+ const allGames = collectAllGames();
+ const game = allGames.find((item) => item.id === gameId);
+ if (!game) {
+ return;
+ }
+
+ setCurrentView("catalogue");
+ if (game.brand && state.brands[game.brand]) {
+ state.selectedBrand = game.brand;
+ }
+ if (game.consoleName) {
+ state.selectedConsole = game.consoleName;
+ }
+
+ // Narrow the catalogue to quickly open the target card and start inline edit.
+ v2ConsoleValue = normalizeText(game.consoleName);
+ v2GenreValue = "";
+ v2SearchTerm = normalizeText(game.title);
+ if (v2SearchInput) {
+ v2SearchInput.value = v2SearchTerm;
+ }
+
+ pendingFocusGameId = gameId;
+ inlineEditingGameId = gameId;
+ editingGameId = null;
+ resetPaging();
+ persist();
+ render();
+}
+
function escapeHtml(text) {
return String(text)
.replaceAll("&", "&")
@@ -1597,6 +1663,12 @@ function renderGames() {
const totalFilteredCount = games.length;
const pageSize = pageSizeForViewport();
+ if (pendingFocusGameId) {
+ const focusIndex = games.findIndex((game) => game.id === pendingFocusGameId);
+ if (focusIndex >= 0) {
+ currentPage = Math.floor(focusIndex / pageSize) + 1;
+ }
+ }
currentTotalPages = Math.max(1, Math.ceil(totalFilteredCount / pageSize));
if (currentPage > currentTotalPages) {
currentPage = currentTotalPages;
@@ -1615,6 +1687,7 @@ function renderGames() {
for (const game of pageGames) {
const card = gameCardTemplate.content.cloneNode(true);
const article = card.querySelector(".game-card");
+ article.dataset.gameId = game.id;
article.classList.add(consoleThemeClass(game.consoleName));
if (inlineEditingGameId === game.id) {
article.classList.add("editing");
@@ -1722,6 +1795,14 @@ function renderGames() {
gamesList.append(card);
}
+
+ if (pendingFocusGameId) {
+ const focusedCard = gamesList.querySelector(`.game-card[data-game-id="${pendingFocusGameId}"]`);
+ if (focusedCard instanceof HTMLElement) {
+ focusedCard.scrollIntoView({ behavior: "smooth", block: "center" });
+ }
+ pendingFocusGameId = null;
+ }
}
function startEditMode(game) {
diff --git a/styles.css b/styles.css
index aa79ba2..c85931d 100644
--- a/styles.css
+++ b/styles.css
@@ -398,6 +398,16 @@ h1 {
border-radius: 10px;
padding: 0.5rem 0.6rem;
background: #ffffff;
+ cursor: pointer;
+ transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
+}
+
+.search-hit:hover,
+.search-hit:focus-visible {
+ border-color: #9fc3e6;
+ box-shadow: 0 0 0 3px rgba(70, 130, 180, 0.16);
+ transform: translateY(-1px);
+ outline: none;
}
.search-hit strong {