import * as React from "react"; import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts"; import { coreMachines, difficultyLevels, machineCategories, searchMachines, type Difficulty, type MachineCategory, type MachineDefinition, } from "../modules/machines/catalogue"; import { useIntegratedPageMeta } from "../production/integrationAdapters"; export interface CataloguePageProps { initialQuery?: string; onOpenMachine?: (machineId: string) => void; } type CategoryFilter = "All" | MachineCategory; type DifficultyFilter = "All" | Difficulty; type SortMode = "name" | "newest" | "complexity"; const FAVOURITES_STORAGE_KEY = "mechanica:favourite-machines"; function readFavourites(): string[] { if (typeof window === "undefined") { return []; } try { const parsed = JSON.parse(window.localStorage.getItem(FAVOURITES_STORAGE_KEY) ?? "[]"); return Array.isArray(parsed) ? parsed.filter((value): value is string => typeof value === "string") : []; } catch { return []; } } function writeFavourites(favourites: readonly string[]): void { if (typeof window === "undefined") { return; } try { window.localStorage.setItem(FAVOURITES_STORAGE_KEY, JSON.stringify([...favourites])); } catch { // Favourites are progressive enhancement; ignore storage failures. } } function difficultyClass(difficulty: Difficulty): string { switch (difficulty) { case "Beginner": return "border-emerald-400/30 bg-emerald-400/10 text-emerald-200"; case "Intermediate": return "border-sky-400/30 bg-sky-400/10 text-sky-200"; case "Advanced": return "border-amber-400/30 bg-amber-400/10 text-amber-200"; } } function sortMachines(machines: readonly MachineDefinition[], sortMode: SortMode): MachineDefinition[] { const copy = [...machines]; if (sortMode === "name") { return copy.sort((left, right) => left.title.localeCompare(right.title)); } if (sortMode === "newest") { return copy.sort((left, right) => right.dateAdded.localeCompare(left.dateAdded)); } return copy.sort((left, right) => right.complexity - left.complexity || left.title.localeCompare(right.title)); } function MachineCardSkeleton(): JSX.Element { return ( ); } function MachineCard({ machine, favourite, onToggleFavourite, onOpenMachine, }: { machine: MachineDefinition; favourite: boolean; onToggleFavourite: (machineId: string) => void; onOpenMachine?: (machineId: string) => void; }): JSX.Element { const href = `/machine/${machine.id}`; const openMachine = (event: React.MouseEvent) => { if (!onOpenMachine) { return; } event.preventDefault(); onOpenMachine(machine.id); }; return (
{machine.category}
C{machine.complexity}

{machine.title}

{machine.description}

{machine.difficulty} {machine.parts.length} parts {machine.keywords[0]}
); } export default function CataloguePage({ initialQuery = "", onOpenMachine, }: CataloguePageProps): JSX.Element { const [ready, setReady] = React.useState(false); const [query, setQuery] = React.useState(initialQuery); const [category, setCategory] = React.useState("All"); const [difficulty, setDifficulty] = React.useState("All"); const [sortMode, setSortMode] = React.useState("name"); const [showFavouritesOnly, setShowFavouritesOnly] = React.useState(false); const [favourites, setFavourites] = React.useState(() => readFavourites()); const searchInputRef = React.useRef(null); useIntegratedPageMeta({ title: "Mechanica Catalogue | Mechanical Systems Explorer", description: "Browse 28 interactive mechanical systems including engines, gearboxes, pumps, mechanisms, bearings, brakes, and turbo machinery.", image: "/social/mechanica-og.svg", type: "website", }); React.useEffect(() => { const timer = window.setTimeout(() => setReady(true), 90); return () => window.clearTimeout(timer); }, []); React.useEffect(() => { setQuery(initialQuery); }, [initialQuery]); React.useEffect(() => { writeFavourites(favourites); }, [favourites]); useKeyboardShortcuts([ { key: "/", label: "/", description: "Focus catalogue search", group: "Catalogue", allowInInputs: false, handler: () => searchInputRef.current?.focus(), }, ]); const filteredMachines = React.useMemo(() => { const searched = searchMachines(query); return sortMachines( searched.filter((machine) => { const categoryMatches = category === "All" || machine.category === category; const difficultyMatches = difficulty === "All" || machine.difficulty === difficulty; const favouriteMatches = !showFavouritesOnly || favourites.includes(machine.id); return categoryMatches && difficultyMatches && favouriteMatches; }), sortMode, ); }, [category, difficulty, favourites, query, showFavouritesOnly, sortMode]); const toggleFavourite = React.useCallback((machineId: string) => { setFavourites((currentFavourites) => currentFavourites.includes(machineId) ? currentFavourites.filter((favouriteId) => favouriteId !== machineId) : [...currentFavourites, machineId], ); }, []); return (

Production catalogue

Explore mechanical systems as interactive engineering references.

Search, filter, bookmark, and launch detailed 3D views with exploded parts, animation playback, cross-sections, annotations, and shareable URLs.

Machines
{coreMachines.length}
Parts
{coreMachines.reduce((total, machine) => total + machine.parts.length, 0)}
Categories
{machineCategories.length}

Showing {filteredMachines.length}{" "} of {coreMachines.length} systems.

Tip: press / to search.

{!ready ? Array.from({ length: 8 }, (_, index) => ) : filteredMachines.map((machine) => ( ))}
{ready && filteredMachines.length === 0 ? (

No machines match those filters.

Try clearing the search, selecting all categories, or disabling the favourites filter.

) : null}
); }