### Description
The community store passed 770 integrations and is heading to a… thousand. This PR scales the **Integrations** page to that size by following one rule, discussed on the forum topic: a filter earns its place when it answers a question a user actually asks while looking for an integration **and** clearly reduces the results. Everything else was left out on purpose.
**Lot 1 — the page, no new data**
- **Ranked search, the first filter.** The search now reads the name, every language of the description, the search-only `keywords` of the manifest, the repository owner (so `guim31` lists an author's integrations without an author filter) and the names of the countries an integration is declared for (`Canada` lists the Canadian ones). Several words are all required, in any order, accents folded. Results are ranked: name starting with the search, then name containing it, then keywords / author, then the rest; the chosen sort orders each group. A search looks through the **whole catalog**: typing on a shelf leaves it for "All integrations", and the country filter is lifted. The inventories (Favorites, Installed, To update) keep their scope.
- **"Installed" badge** on the cards of the browse views (the Installed view keeps showing the live status instead).
- **"Last updated" sort**, third option of the existing selector, from the `github.pushed_at` the lite index already carries. Natives sink as an undated block, like under "Newest first".
- **Count compared to the whole catalog** ("42 of 810 integrations" as soon as a shelf, a facet or a search narrows it) with a single **"Clear filters"** link. It also ends the trap of a facet kept from one category to the next that empties the list.
- **Back to the same place**: clicking a card remembers the catalog URL, the pages of cards rendered and the scroll position; the catalog mounted again on that URL renders the same pages and scrolls back once the store has loaded.
**Lot 2 — four categories, flat list**
`transport` (Transport & mobility), `vehicles` (Vehicles & EV charging), `servers` (Servers & self-hosting), `leisure` (Leisure, sport & games). On the live index, `services` had become a 160-entry catch-all, `energy` carried every car and charger, `network` every home server, and public transport had no shelf although a dozen networks are published. No nesting. Weather and heating were deliberately not split: the country filter slims them down.
**Lot 3 — two manifest fields and the country filter nobody sets**
- `keywords` (1..10 search-only terms) and `countries` (1..20 ISO 3166-1 alpha-2 codes, absent = worldwide): validator, vendored schema, lite index allowlist, server-side catalog search.
- The **country of the instance** is derived from the `TIMEZONE` system variable (browser time zone as fallback) through a static table generated from the IANA database (`front/cli/generate-timezone-countries.js`, from `zone.tab` so a Dutch instance is not placed in Belgium). The browse views show the worldwide integrations and the ones declared for that country. The filter is invisible until it hides something: one line then reads "Shown for France: 12 integrations from other countries are hidden · Show all countries" (`?country=all`). Never applied to a search or to the inventories; nothing is hidden when the country is unknown or when no integration declares countries yet.
**Specs updated in the same diff**: `docs/specs/integration-catalog-categories.md` (§3 vocabulary, §4 "Scaling to a thousand integrations", §5, §6.2, §8 phase D), `docs/specs/external-integrations/contracts/manifest.md` (C.1), `contracts/indexer-formats.md` (C.6), `core/frontend.md` (B.8).
**What the `GladysAssistant/integration-store` repo needs next** (separate PR, like phase A of the categories spec): the canonical schema with `keywords` / `countries`, a `countries` fallback mapping seeded over the existing catalog, and the reassignment of the existing entries to the four new keys in the `categories` fallback mapping. Until then the new keys hold the natives and the manifests that declare them, and the country filter hides nothing.
Deliberately not done, per the forum discussion: "without Gladys Plus" and "not installed" filters, an author filter, a minimum Gladys version filter, a tested / untested status, an install counter, nested sub-categories.
**Verification**: server `npm run coverage` green (changed lines covered), front `npm run build`, `eslint`, `prettier-check` and `compare-translations` green. Cypress `ExternalIntegrationCardUpdate.cy.js`: 4 of 5 pass locally; the failing one ("updates an integration from its card and says so") fails identically on the master build in the same environment, so it is not introduced here. Checked in a real browser against the live store: ranked search, global search leaving the shelf, "N of total · Clear filters", "Last updated" sort.
### Related request
Forum: https://community.gladysassistant.com/t/filtrer-la-page-des-integrations-externes/11228
### Checklist
- [x] If a forum topic or GitHub issue exists, the description links it (`Forum: https://community.gladysassistant.com/t/...` or `Closes #...`)
- [x] Tests pass: `cd server && npm run coverage` (Codecov requires 100% coverage on changed lines) and Cypress (`npm run cypress:run`) if the UI changed
- [x] Linter and prettier pass on both front and server (`npm run eslint`, `npm run prettier`)
- [x] No undocumented breaking change
---
_Generated by [Claude Code](https://claude.ai/code/session_01Gwkcw7FKV3WFJ2RrFZNdZB)_