games: active vs non-active games (games.library.active, owner toggles on the dashboard and each game ⋯ menu; nothing deleted). Header flyout and mobile menu list every game split into active / non-active; /trackers and home tiles show active chips and open both sections on hover; /start shows active with a non-active toggle. Games dashboard moved from /projects/game-tracker to /trackers/games (dashboard of active games only; old URL and /credits 301); deep trackers stay put
All checks were successful
Deploy production / deploy (push) Successful in 1m13s

This commit is contained in:
2026-10-25 20:23:15 +00:00
parent 60370d4572
commit 3ad13e5828
31 changed files with 31471 additions and 565 deletions

View File

@@ -41,7 +41,7 @@
// Same list, order and icons as the header Trackers dropdown (owner-only ones only when
// the header shows them). Spotting is a group tile with one-click chips per sub-tracker.
const trackers = $derived(visibleTrackers(data.isOwner));
const trackers = $derived(visibleTrackers(data.isOwner, data.inactiveGames));
// Search-only shortcuts that aren't trackers of their own.
const extraHits = [
{ name: 'Weigh-in', href: '/trackers/health#body' },
@@ -249,6 +249,20 @@
? (t.children ?? []).filter((c) => c.featured)
: (t.children ?? []);
const hiddenChildren = (t: Group) => (t.children?.length ?? 0) - tileChildren(t).length;
// Games split like the header's Trackers dropdown: "active" (my pick, always shown) and
// "non-active" (behind its own toggle, shown while filtering).
type Child = NonNullable<Group['children']>[number];
const splitGroup = (t: Group) => (t.children ?? []).some((c) => c.inactive);
const chipSections = (t: Group): { label: string; items: readonly Child[] }[] => {
const all = t.children ?? [];
if (!splitGroup(t)) return [{ label: '', items: tileChildren(t) }];
const off = all.filter((c) => c.inactive);
return [
{ label: 'active', items: all.filter((c) => !c.inactive) },
{ label: 'non-active', items: filtering || expandedGroups[t.name] ? off : [] }
];
};
const inactiveCount = (t: Group) => (t.children ?? []).filter((c) => c.inactive).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();
@@ -571,12 +585,35 @@
<span class="tname">{t.name}</span>{#if t.locked}<PrivateLock tight />{/if}
</a>
<span class="tchips">
{#each tileChildren(t) as c (c.name)}
<a class="tchip" href={c.href} title={c.subtitle}
><TrackerIcon name={c.icon ?? c.name} class="t-icon size-5" />{c.name}</a
>
{#each chipSections(t) as section (section.label)}
{#if section.label === 'non-active' && !filtering}
<button
type="button"
class="tlabel toggle"
aria-expanded={!!expandedGroups[t.name]}
onclick={() =>
(expandedGroups = {
...expandedGroups,
[t.name]: !expandedGroups[t.name]
})}
>non-active <span>{inactiveCount(t)}</span>{expandedGroups[t.name]
? ' ▾'
: ' ▸'}</button
>
{:else if section.label && section.items.length}
<span class="tlabel">{section.label} <span>{section.items.length}</span></span
>
{/if}
{#each section.items as c (c.name)}
<a
class={['tchip', c.inactive && 'inactive']}
href={c.href}
title={c.subtitle}
><TrackerIcon name={c.icon ?? c.name} class="t-icon size-5" />{c.name}</a
>
{/each}
{/each}
{#if !filtering && (hiddenChildren(t) > 0 || expandedGroups[t.name])}
{#if !filtering && !splitGroup(t) && (hiddenChildren(t) > 0 || expandedGroups[t.name])}
<button
type="button"
class="tchip more"
@@ -1670,6 +1707,38 @@
.stretched:hover {
text-decoration: none;
}
.tlabel {
flex-basis: 100%;
display: flex;
align-items: baseline;
gap: 0.35rem;
font-size: 0.62rem;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--site-muted);
background: none;
border: 0;
padding: 0;
text-align: left;
}
.tlabel span {
letter-spacing: 0;
opacity: 0.7;
}
.tlabel:not(:first-child) {
margin-top: 0.3rem;
}
button.tlabel {
cursor: pointer;
}
button.tlabel:hover {
color: inherit;
}
.tchip.inactive :global(.t-icon) {
filter: grayscale(0.6);
opacity: 0.75;
}
.tchips {
position: relative;
z-index: 1;