Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions src/features/discovery/Shelves.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ const ROWS: { key: keyof ShelvesData; title: string; note?: string }[] = [
{ key: "featured", title: "Featured" },
{ key: "staff", title: "Staff picks" },
{ key: "new", title: "New this week", note: "Every new game shows here first" },
{ key: "most_played", title: "Most played", note: "Players who stay, not just players who click" },
{ key: "most_improved", title: "Most improved", note: "Growing the fastest" },
];

const PER_SHELF = 5; // one row of the same cards as the games grid

/** The homepage shelves (PRD A1): a row of games per shelf, from /discover/shelves. A shelf with no games isn't shown. */
export default function Shelves({ games, user }: PagePartProps) {
const [shelves, setShelves] = useState<ShelvesData | null>(null);
Expand All @@ -22,7 +22,7 @@ export default function Shelves({ games, user }: PagePartProps) {
}, [user?.id]);
if (!shelves || !games) return null;
const bySlug = new Map(games.map((g) => [g.slug, g]));
const rows = ROWS.map((r) => ({ ...r, games: shelves[r.key].map((n) => bySlug.get(n)).filter((g): g is Game => !!g) })).filter((r) => r.games.length > 0);
const rows = ROWS.map((r) => ({ ...r, games: shelves[r.key].map((n) => bySlug.get(n)).filter((g): g is Game => !!g).slice(0, PER_SHELF) })).filter((r) => r.games.length > 0);
if (rows.length === 0) return null;
return (
<section className="container shelves" aria-label="Find a game">
Expand All @@ -31,13 +31,15 @@ export default function Shelves({ games, user }: PagePartProps) {
<h2>{r.title} {r.note && <span className="note">{r.note}</span>}</h2>
<ul>
{r.games.map((g) => (
<li key={g.slug}>
<a href={`/games/${g.slug}`}>
<span className="art" style={{ background: `linear-gradient(135deg, ${g.colors[0]}, ${g.colors[1]})` }}>
<li key={g.slug} className="game">
<a className="game-link" href={`/games/${g.slug}`}>
<div className="art" style={{ background: `linear-gradient(135deg, ${g.colors[0]}, ${g.colors[1]})` }}>
{g.cover ? <img src={g.cover} alt="" loading="lazy" decoding="async" /> : <span>{g.title.charAt(0)}</span>}
</span>
<b>{g.title}</b>
<em>by {g.by}</em>
</div>
<div className="info">
<h3>{g.title}</h3>
<p>by {g.by}</p>
</div>
</a>
</li>
))}
Expand Down
16 changes: 9 additions & 7 deletions src/features/discovery/discovery.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
.shelves { display: grid; gap: 22px; margin: 8px auto 28px; }
.shelf h2 { font-size: 1.1rem; margin: 0 0 8px; display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
.shelf ul { list-style: none; margin: 0; padding: 0 0 6px; display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x proximity; }
.shelf li { flex: 0 0 150px; scroll-snap-align: start; }
.shelf a { display: grid; gap: 4px; color: inherit; text-decoration: none; }
.shelf .art { display: grid; place-items: center; height: 96px; border-radius: 12px; overflow: hidden; font-weight: 800; font-size: 1.6rem; color: #fff; }
.shelf .art img { width: 100%; height: 100%; object-fit: cover; }
.shelf b { font-size: .92rem; }
.shelf em { font-style: normal; font-size: .76rem; color: var(--muted); }
.shelf ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; grid-template-columns: repeat(5, minmax(0, 1fr)); }
.shelf .art span { font-size: 2.4rem; }
.shelf .info { padding: 0 4px 4px; }
.shelf .info h3 { font-size: 1rem; }
/* a livelier pop than the grid cards: lifts higher, grows a little and overshoots slightly before it settles */
.shelf .game { transition: transform .28s cubic-bezier(.34, 1.56, .64, 1), border-color .15s, box-shadow .15s; }
.shelf .game:hover { transform: translateY(-8px) scale(1.05); border-color: var(--ink); box-shadow: 6px 8px 0 var(--lime); }
@media (prefers-reduced-motion: reduce) { .shelf .game { transition: none; } .shelf .game:hover { transform: none; } }
@media (max-width: 600px) { .shelf ul { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } }
.play-next a { white-space: nowrap; }
.feature-checklist { margin-top: 8px; font-size: .9rem; }
.feature-checklist summary { cursor: pointer; font-weight: 600; }
Expand Down
4 changes: 2 additions & 2 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -576,8 +576,8 @@ code { color: var(--on-lime); }
.tournament-banner-sub { margin: 0; color: var(--muted); }
.tournament-banner .btn { grid-area: cta; justify-self: start; }
.tournament-banner-games { grid-area: games; display: flex; gap: 8px; }
.tournament-banner-games .art { width: 72px; aspect-ratio: 1; border-radius: 12px; border: 1px solid var(--line); }
.tournament-banner-games .art span { font-size: 1.6rem; }
.tournament-banner-games .art { width: 96px; aspect-ratio: 1; border-radius: 14px; border: 1px solid var(--line); }
.tournament-banner-games .art span { font-size: 2.2rem; }
.tournament-page { padding-top: 24px; padding-bottom: 72px; min-height: 60vh; display: grid; gap: 22px; align-content: start; }
.tournament-page h1 { font-size: 2.4rem; letter-spacing: -.04em; margin: 0; }
.tournament-page .section-head { margin-bottom: 0; }
Expand Down
Loading