feat: polish homepage catalog stats
This commit is contained in:
@@ -28,8 +28,7 @@ const {
|
||||
</a>
|
||||
<nav aria-label="Primary navigation">
|
||||
<a href="/">Catalog</a>
|
||||
<a href="/browse/type/difficulty/">Difficulty</a>
|
||||
<a href="/browse/platform/gba/">GBA</a>
|
||||
<a href="/#browse">Browse</a>
|
||||
<a href="/queue/">Play Queue</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
+104
-6
@@ -2,12 +2,73 @@
|
||||
import HackCard from '../components/HackCard.astro';
|
||||
import FilterGroup from '../components/FilterGroup.astro';
|
||||
import Layout from '../components/Layout.astro';
|
||||
import { catalog, flagshipHacks, getFacetEntries, hacks } from '../lib/site-data';
|
||||
import { catalog, facetValueSlug, flagshipHacks, getFacetEntries, hacks } from '../lib/site-data';
|
||||
import type { FacetName } from '../lib/types';
|
||||
|
||||
const platforms = getFacetEntries('platform');
|
||||
const bases = getFacetEntries('base').slice(0, 12);
|
||||
const types = getFacetEntries('type');
|
||||
const statuses = getFacetEntries('status');
|
||||
|
||||
type StatItem = {
|
||||
label: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
type StatGroup = {
|
||||
label: string;
|
||||
icon: string;
|
||||
items: StatItem[];
|
||||
};
|
||||
|
||||
type BrowseGroup = {
|
||||
label: string;
|
||||
facet: Extract<FacetName, 'platform' | 'type' | 'status'>;
|
||||
entries: [string, number][];
|
||||
};
|
||||
|
||||
function facetCount(facet: FacetName, value: string): number {
|
||||
return catalog.facets[facet]?.[value] ?? 0;
|
||||
}
|
||||
|
||||
function displayFacetValue(value: string): string {
|
||||
return value === 'Unknown' ? 'Unclassified' : value;
|
||||
}
|
||||
|
||||
function selectedItems(facet: FacetName, values: string[]): StatItem[] {
|
||||
return values
|
||||
.map((value) => ({ label: displayFacetValue(value), count: facetCount(facet, value) }))
|
||||
.filter((item) => item.count > 0);
|
||||
}
|
||||
|
||||
function otherItem(facet: FacetName, selectedValues: string[]): StatItem | undefined {
|
||||
const selected = new Set([...selectedValues, 'Unknown']);
|
||||
const count = getFacetEntries(facet)
|
||||
.filter(([value]) => !selected.has(value))
|
||||
.reduce((total, [, entryCount]) => total + entryCount, 0);
|
||||
return count > 0 ? { label: 'Other', count } : undefined;
|
||||
}
|
||||
|
||||
function summaryItems(facet: FacetName, values: string[]): StatItem[] {
|
||||
const items = selectedItems(facet, values);
|
||||
const other = otherItem(facet, values);
|
||||
const unknown = facetCount(facet, 'Unknown');
|
||||
if (other) items.push(other);
|
||||
if (unknown > 0) items.push({ label: 'Unclassified', count: unknown });
|
||||
return items;
|
||||
}
|
||||
|
||||
const statGroups: StatGroup[] = [
|
||||
{ label: 'Platforms', icon: 'platform', items: summaryItems('platform', ['GBA', 'NDS', 'PC', 'GBC']) },
|
||||
{ label: 'Types', icon: 'type', items: summaryItems('type', ['Expansion', 'New Experience', 'Difficulty', 'QoL']) },
|
||||
{ label: 'Status', icon: 'status', items: summaryItems('status', ['Complete', 'Ongoing', 'Beta']) },
|
||||
];
|
||||
|
||||
const browseGroups: BrowseGroup[] = [
|
||||
{ label: 'Platforms', facet: 'platform', entries: platforms },
|
||||
{ label: 'Types', facet: 'type', entries: types },
|
||||
{ label: 'Status', facet: 'status', entries: statuses },
|
||||
];
|
||||
---
|
||||
|
||||
<Layout title="ROM Hack Archive">
|
||||
@@ -16,11 +77,26 @@ const statuses = getFacetEntries('status');
|
||||
<h1>ROM Hack Archive</h1>
|
||||
<p>A private catalog of {catalog.count} Pokémon ROM hacks, fan-games, patches, notes, guides, and files.</p>
|
||||
</div>
|
||||
<div class="hero-stats" aria-label="Catalog totals">
|
||||
<div><strong>{catalog.count}</strong><span>hacks</span></div>
|
||||
<div><strong>{catalog.facets.platform.GBA ?? 0}</strong><span>GBA</span></div>
|
||||
<div><strong>{catalog.facets.type.Expansion ?? 0}</strong><span>expansions</span></div>
|
||||
</div>
|
||||
<details class="hero-stats-panel" open>
|
||||
<summary>Catalog breakdown</summary>
|
||||
<div class="hero-stats" aria-label="Catalog totals">
|
||||
<div class="stat-card stat-card-total"><strong>{catalog.count}</strong><span>hacks</span></div>
|
||||
{statGroups.map((group) => (
|
||||
<div class="stat-card stat-card-group">
|
||||
<span class="stat-heading">{group.label}</span>
|
||||
<ul class="stat-list">
|
||||
{group.items.map((item) => (
|
||||
<li>
|
||||
<span class={`stat-icon stat-icon-${group.icon}`} aria-hidden="true"></span>
|
||||
<span class="stat-label">{item.label}</span>
|
||||
<strong>{item.count}</strong>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<section class="flagships" aria-labelledby="flagships-heading">
|
||||
@@ -33,6 +109,28 @@ const statuses = getFacetEntries('status');
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="browse" class="browse-summary" aria-label="Browse by catalog facets">
|
||||
<div class="section-heading">
|
||||
<h2>Browse the archive</h2>
|
||||
<p>Jump into grouped catalog views without privileging a single platform or play style.</p>
|
||||
</div>
|
||||
<div class="browse-summary-grid">
|
||||
{browseGroups.map((group) => (
|
||||
<section class="browse-facet" aria-labelledby={`browse-${group.facet}`}>
|
||||
<h3 id={`browse-${group.facet}`}>{group.label}</h3>
|
||||
<div class="browse-link-list">
|
||||
{group.entries.map(([value, count]) => (
|
||||
<a href={`/browse/${group.facet}/${facetValueSlug(value)}/`}>
|
||||
<span>{displayFacetValue(value)}</span>{' '}
|
||||
<em>{count}</em>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="catalog-shell" aria-label="Hack catalog">
|
||||
<aside class="filter-rail">
|
||||
<div class="filter-panel">
|
||||
|
||||
Reference in New Issue
Block a user