diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbbf08f5..095d3e1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,10 +9,11 @@ Since the `1.0.0-beta` release, the version in `package.json` has stayed at `1.0
**Monthly batching (agents):** Use **one** `## [1.0.0-ci] - YYYY-MM-DD` header per calendar month. Before editing, search this file for `- YYYY-MM-` (today’s year-month); if a section exists, append bullets under `### Added` / `### Changed` / `### Fixed` there and set that header’s date to today — do **not** add a second `##` for the same month. A new ship day is not a new section.
-## [1.0.0-ci] - 2026-11-06
+## [1.0.0-ci] - 2026-11-07
### Added
+- **Navbar game search:** a magnifying glass in the navbar finds games by title (or designer) from any page and opens the game page, with keyboard navigation.
- **Game wishlist:** anyone signed in can **add a missing cover image** on a wishlist entry’s detail page; **admins** can **replace** the single cover image. (Requires matching `feedback_update` rules on the API.)
- **Explore (all games):** quick **filter by tag** dropdown plus **Advanced filters** modal (combine goals, mechanics, board, and other tags with any/all/none; optional OR groups; global exclusions). Tag filters can be bookmarked via URL query parameters (`t`, `tf`, and related keys). Name, designer, star, and recommended filters are unchanged.
- **Site statistics (games tab):** game statistics and pie rule tables include a **Min. records** control to hide rows below a chosen record count (remembered per browser).
diff --git a/docs/subsystems/explore.md b/docs/subsystems/explore.md
index 0eceea9d..07e701d0 100644
--- a/docs/subsystems/explore.md
+++ b/docs/subsystems/explore.md
@@ -13,6 +13,10 @@ The explore page lists available games and meta-games.
Legacy bookmarks to `/games/:mode` where `:mode` is a catalog view slug redirect to `/explore/:mode`.
+## Navbar game search
+
+The magnifying glass in the navbar ([`GameSearch.js`](../src/components/GameSearch.js)) is the quick route to a game page. It filters the public catalog with `filterGameOptions` (the matching used by the game picker) and orders results with `rankGameSearchResults`, so title matches come first. Selecting a result navigates to `/games/:metaGame`.
+
## Key files
| File | Role |
diff --git a/public/locales/en/apfront.json b/public/locales/en/apfront.json
index 6fcd12be..c8d1ed40 100644
--- a/public/locales/en/apfront.json
+++ b/public/locales/en/apfront.json
@@ -1245,6 +1245,7 @@
"toggleDarkMode": "Toggle dark mode",
"toggleLightMode": "Toggle light mode",
"notifications": "Notifications",
+ "searchGames": "Search games",
"moveUp": "move up",
"moveDown": "move down"
},
@@ -2392,6 +2393,9 @@
"recent": "Recent opponents"
}
},
+ "gameSearch": {
+ "placeholder": "Search games…"
+ },
"gamePicker": {
"allGames": "All games",
"choose": "Choose a game…",
diff --git a/src/components/GameSearch.js b/src/components/GameSearch.js
new file mode 100644
index 00000000..eef19da6
--- /dev/null
+++ b/src/components/GameSearch.js
@@ -0,0 +1,160 @@
+import {
+ useCallback,
+ useEffect,
+ useId,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { useTranslation } from "react-i18next";
+import { Link, useNavigate } from "react-router-dom";
+import NavDropdownPanel from "./NavDropdownPanel";
+import {
+ buildGameBrowseEntries,
+ filterGameOptions,
+ rankGameSearchResults,
+} from "../lib/gameOptions";
+
+/** Navbar magnifying glass: find a game by title and jump to its page. */
+function GameSearch({ closeBurger }) {
+ const { t, i18n } = useTranslation();
+ const navigate = useNavigate();
+ // The navbar renders one instance for desktop and one for mobile.
+ const listId = useId();
+ const triggerRef = useRef(null);
+ const listRef = useRef(null);
+ const [open, setOpen] = useState(false);
+ const [query, setQuery] = useState("");
+ const [activeIndex, setActiveIndex] = useState(0);
+
+ const games = useMemo(
+ () => (open ? buildGameBrowseEntries({ locale: i18n.language }) : []),
+ [open, i18n.language]
+ );
+ const results = useMemo(
+ () => rankGameSearchResults(filterGameOptions(games, { query }), query),
+ [games, query]
+ );
+ const hasQuery = query.trim() !== "";
+
+ useEffect(() => {
+ listRef.current?.children[activeIndex]?.scrollIntoView?.({
+ block: "nearest",
+ });
+ }, [activeIndex]);
+
+ const close = useCallback(() => {
+ setOpen(false);
+ setQuery("");
+ setActiveIndex(0);
+ }, []);
+
+ const toggle = () => {
+ if (open) {
+ close();
+ } else {
+ closeBurger();
+ setOpen(true);
+ }
+ };
+
+ const handleKeyDown = (event) => {
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
+ event.preventDefault();
+ if (results.length > 0) {
+ const step = event.key === "ArrowDown" ? 1 : -1;
+ setActiveIndex(
+ (index) => (index + step + results.length) % results.length
+ );
+ }
+ } else if (event.key === "Enter" && results[activeIndex]) {
+ event.preventDefault();
+ navigate(`/games/${results[activeIndex].id}`);
+ close();
+ } else if (event.key === "Escape") {
+ close();
+ triggerRef.current?.focus();
+ }
+ };
+
+ return (
+
+
+ {open ? (
+