--- import Layout from '../layouts/Layout.astro'; import CategoryFilter from '../components/CategoryFilter.astro'; import Card from '../components/Card.astro'; import Icon from '../components/Icon.astro'; import { getDB, listPlugins, searchPlugins, type Plugin } from '../lib/db'; import { EMPTY_STATE, SITE, orderCategories } from '../lib/site'; export const prerender = false; // Plugin details used to open in a client-side drawer keyed by ?ext=/?id=. Those // deep links now belong to the dedicated /plugin/[id] page, so send them there // instead of rendering the catalog with a query nothing acts on. const legacyId = Astro.url.searchParams.get('ext') ?? Astro.url.searchParams.get('id'); if (legacyId) { return Astro.redirect(`/plugin/${encodeURIComponent(legacyId)}`, 301); } // A ?q= search is answered by D1 so the results are real at first paint (and the // grid is populated before the client script runs). The navbar pre-fills the input // with the same value, so the box and the results never disagree on load. const searchParam = Astro.url.searchParams.get('q')?.trim() ?? ''; const isSearching = searchParam.length > 0; let plugins: Plugin[] = []; let dbError: string | null = null; try { const db = getDB(Astro.locals); plugins = isSearching ? await searchPlugins(db, searchParam) : await listPlugins(db); } catch (error) { dbError = error instanceof Error ? error.message : String(error); } const categories = orderCategories(plugins.map((plugin) => plugin.category)); const platforms = [...new Set(plugins.flatMap((plugin) => plugin.platforms))].sort(); const categoryCounts = plugins.reduce>((acc, plugin) => { acc[plugin.category] = (acc[plugin.category] ?? 0) + 1; return acc; }, {}); const total = plugins.length; ---

Live catalog ยท served from Cloudflare D1

Extend your OpenROM preservation workflow

{SITE.description} Every download count below is a real, live number โ€” nothing here is simulated.

ROMeo, the OpenROM mascot, standing in a retro arcade shop
{ dbError ? (

Catalog temporarily unavailable

The D1 database could not be reached. Please try again shortly.

{dbError}

) : total === 0 && !isSearching ? (
ROMeo, the OpenROM mascot, looking puzzled through a magnifying glass

{EMPTY_STATE.title}

{EMPTY_STATE.body}

{EMPTY_STATE.cta}
) : (

{total} {total === 1 ? ' extension' : ' extensions'} {isSearching && for โ€œ{searchParam}โ€}

Sorted live from D1

{plugins.map((plugin) => ( ))}
) }