--- /** * The Shelf: every board game. What is on the table (from the status log), * then the collection, filtered by how it plays, owned, want to play and * favorites, and sorted by last played, A to Z or recently added. Player * counts, play times and box art sync from BoardGameGeek (a cover set in the * note wins); no filter for them yet. */ import FilterGroup from "@gk/ui/components/FilterGroup.astro"; import GiltCorners from "@gk/ui/components/GiltCorners.astro"; import PageShell from "@gk/ui/components/PageShell.astro"; import SearchField from "@gk/ui/components/SearchField.astro"; import Star from "@gk/ui/components/Star.astro"; import StarChartArt from "@gk/ui/components/StarChartArt.astro"; import TickScale from "@gk/ui/components/TickScale.astro"; import FilterLayout from "../../components/library/FilterLayout.astro"; import GameCard from "../../components/library/GameCard.astro"; import LibraryHero from "../../components/library/LibraryHero.astro"; import RailGroup from "../../components/library/RailGroup.astro"; import { gameShows, minutesOf, onTheTable, playerOptions, playersOf, playStyles, searchText, shelfFilter, sortGames, sorts, timeOptions, type Game } from "../../lib/library.ts"; import { site } from "../../lib/page.ts"; import { formatDate, mediaSrc } from "../../lib/site.ts"; const s = await site(); const f = shelfFilter(Astro.url.searchParams); const all = s.all("board-game"); const games = sortGames(all, f.sort); const styles = playStyles(all); const owned = all.filter((g) => g.data.owned).length; const lede = s.pageText("/library/shelf")?.data.lede ?? "My board game collection. Filter by how it plays, sort by what hit the table last."; const text = (g: Game) => searchText([g.title, g.data.blurb, ...(g.data.howItPlays ?? [])]); const hasPost = (g: Game) => s.backlinks(g, { type: "post" }).length > 0; const visible = new Set(games.filter((g) => gameShows(g, f, text(g), hasPost(g))).map((g) => g.slug)); // Players and play time come from BoardGameGeek; their pills wait for a sync. const synced = { players: all.some((g) => playersOf(g)), time: all.some((g) => minutesOf(g) !== undefined) }; const anyPost = all.some(hasPost); /** Games shown before "Show all", while no filter narrows the list. */ const FIRST = 12; const narrowed = !!(f.q || f.plays.size || f.want || !f.owned || f.favorites || f.blog || f.players || f.time); const folded = !narrowed && visible.size > FIRST; const firstShown = new Set(games.filter((g) => visible.has(g.slug)).slice(0, FIRST).map((g) => g.slug)); // On the table: board-game statuses, then the most recent play not already listed. const table: { label: string; game: Game; note?: string; live: boolean }[] = onTheTable(s).map(({ status, game }) => ({ label: `${status.data.kind} now`, game, note: status.data.note, live: true, })); const recent = sortGames(all, "played").find((g) => g.data.lastPlayed && !table.some((t) => t.game.path === g.path)); if (recent) table.push({ label: "Played recently", game: recent, note: `Last played ${formatDate(recent.data.lastPlayed)}`, live: false }); const sortHref = (key: string) => { const p = new URLSearchParams(Astro.url.searchParams); p.set("sort", key); return `/library/shelf?${p}`; }; ---

On the table

{ table.length > 0 ? ( ) : (

Nothing on the table right now.

) }
{ styles.length > 0 && ( {styles.map((p) => )} ) } { synced.players && ( {playerOptions.map((o) => )} ) } { synced.time && ( {timeOptions.map((o) => )} ) } {anyPost && } {(synced.players || synced.time) &&

Player counts and play times come from BoardGameGeek.

}

Showing {visible.size} {visible.size === 1 ? "game" : "games"}

({ label: o.label, href: sortHref(o.key), active: o.key === f.sort, value: o.key }))} />
    {games.map((g) =>