games: per-game pages, games.library table, games group in header/tiles/start
All checks were successful
Deploy production / deploy (push) Successful in 1m7s

This commit is contained in:
2026-10-25 15:20:45 +00:00
parent 037a525f91
commit d79b73ad83
19 changed files with 27712 additions and 38 deletions

View File

@@ -63,6 +63,14 @@
});
const filtering = $derived(linkFilter.trim() !== '');
// Big groups (games): featured chips first, the rest behind "+N more" (all while filtering).
let expandedGroups = $state<Record<string, boolean>>({});
type Group = (typeof trackers)[number];
const tileChildren = (t: Group) =>
filtering || expandedGroups[t.name]
? (t.children ?? [])
: (t.children ?? []).filter((c) => c.featured ?? true);
const hiddenChildren = (t: Group) => (t.children?.length ?? 0) - tileChildren(t).length;
// The links filter (f) also narrows the trackers; a group stays if it or a chip matches.
const shownTrackers = $derived.by(() => {
const q = linkFilter.trim().toLowerCase();
@@ -291,7 +299,7 @@
</h2>
<ul class="tgrid">
{#each shownTrackers as t (t.name)}
<li class={{ group: !!t.children }}>
<li class={{ group: !!t.children, wide: (t.children?.length ?? 0) > 6 }}>
{#if t.children}
<!-- Group tile: stretched main link, chips layered above it (no nested <a>). -->
<div class="ttile">
@@ -300,11 +308,21 @@
<span class="tname">{t.name}</span>
</a>
<span class="tchips">
{#each t.children as c (c.name)}
{#each tileChildren(t) as c (c.name)}
<a class="tchip" href={c.href} title={c.subtitle}
><TrackerIcon name={c.name} class="t-icon size-5" />{c.name}</a
><TrackerIcon name={c.icon ?? c.name} class="t-icon size-5" />{c.name}</a
>
{/each}
{#if !filtering && (hiddenChildren(t) > 0 || expandedGroups[t.name])}
<button
type="button"
class="tchip more"
aria-expanded={!!expandedGroups[t.name]}
onclick={() =>
(expandedGroups = { ...expandedGroups, [t.name]: !expandedGroups[t.name] })}
>{hiddenChildren(t) > 0 ? `+${hiddenChildren(t)} more` : 'less'}</button
>
{/if}
</span>
</div>
{:else}
@@ -1039,11 +1057,15 @@
.tgrid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
grid-auto-flow: row dense;
gap: 6px;
}
.tgrid > li.group {
grid-column: span 4;
}
.tgrid > li.group.wide {
grid-column: span 6;
}
.ttile {
position: relative;
display: flex;
@@ -1122,6 +1144,17 @@
border-color 160ms ease,
background-color 160ms ease;
}
button.tchip {
padding: 2px 9px;
color: var(--site-muted);
cursor: pointer;
font: inherit;
font-size: 12px;
font-weight: 500;
}
button.tchip:hover {
color: var(--site-text);
}
.tchip :global(.t-icon) {
border-radius: 999px;
}
@@ -1145,6 +1178,14 @@
transform: none !important;
}
}
@media (max-width: 1000px) {
.tgrid > li.group.wide {
grid-column: 1 / -1;
}
div.ttile {
flex-wrap: wrap;
}
}
@media (max-width: 760px) {
.tgrid > li.group {
grid-column: 1 / -1;