Fix sleep bug, fix menu return
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m1s
Traceability Validation / Check Requirement Traces (push) Successful in 23s
Build & Release / Run Tests (push) Successful in 4m7s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 19m5s
Build & Release / Build Linux (push) Successful in 16m20s
Build & Release / Build Android (push) Successful in 19m12s
Build & Release / Create Release (push) Successful in 8s

This commit is contained in:
2026-07-01 23:49:51 +02:00
parent 342f95cac1
commit 75014ee00f
22 changed files with 880 additions and 149 deletions
+59
View File
@@ -0,0 +1,59 @@
// Hero banner mix builder, shared by the movies/TV/music landing stores.
//
// The hero used to show the first N items of fixed pools (continue watching,
// recently played, latest), which made it identical on every visit. Instead we
// sample a couple of items at random from each pool — pools are ordered most
// personal first — and shuffle the rotation, so the banner is a fresh mix each
// time while still leading with something personal.
// TRACES: UR-034 | DR-038
import type { MediaItem } from "$lib/api/types";
/** Fisher-Yates shuffle; returns a new array, input is untouched. */
export function shuffle<T>(items: T[]): T[] {
const result = [...items];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
/**
* Build a varied hero rotation from ordered pools (most personal first).
*
* Takes up to `perPool` random items from each pool, de-duplicated by id and
* filtered to items with usable artwork. The first pick from the first
* non-empty pool leads the rotation (so a resumable/recent item greets the
* user), the rest are shuffled. If that yields fewer than `count` items, the
* remainder is backfilled from whatever is left across all pools.
*/
export function buildHeroMix(
pools: MediaItem[][],
hasArt: (item: MediaItem) => boolean,
count = 6,
perPool = 2
): MediaItem[] {
const seen = new Set<string>();
const usable = pools.map(pool =>
pool.filter(item => {
if (!hasArt(item) || seen.has(item.id)) return false;
seen.add(item.id);
return true;
})
);
const picked = new Set<string>();
const picks: MediaItem[] = [];
for (const pool of usable) {
for (const item of shuffle(pool).slice(0, perPool)) {
picked.add(item.id);
picks.push(item);
}
}
if (picks.length === 0) return [];
const [leader, ...rest] = picks;
const leftovers = shuffle(usable.flat().filter(item => !picked.has(item.id)));
return [leader, ...shuffle(rest), ...leftovers].slice(0, count);
}