Home: status board instead of a feed
- replace 'Recently played' with one card per anime grouped by state: Continue watching (<=6, progress + ~Xm left), On hold (<=4, dimmed, days ago), Recently finished (<=6, check) - hero button says 'Next ep' when the last ep is >=95% watched - empty home shows recent searches as clickable chips (race-guarded: only appended while the empty state is still on screen) - helpers: byShow (latest row per anime), homeCard, showSec, link
This commit is contained in:
1 parent
1de58629a0
commit
bb825a3acb
2 files changed
+63
-35
No files matched your search
+62
-35
@@ -85,10 +85,60 @@ const bar = (x, cls) => {
|
|||||||
return b;
|
return b;
|
||||||
};
|
};
|
||||||
const histHref = x => `?anime=${x.slug_id}&season=${x.season_id}&ep=${x.ep_id}&lang=${x.lang}`;
|
const histHref = x => `?anime=${x.slug_id}&season=${x.season_id}&ep=${x.ep_id}&lang=${x.lang}`;
|
||||||
|
// one entry per anime (latest row wins), newest first
|
||||||
|
const byShow = () => {
|
||||||
|
const m = new Map();
|
||||||
|
for (const x of history) {
|
||||||
|
const k = x.slug_id + ":" + x.season_id;
|
||||||
|
if (!m.has(k) || x.t > m.get(k).t) m.set(k, x);
|
||||||
|
}
|
||||||
|
return [...m.values()].sort((a, b) => b.t - a.t);
|
||||||
|
};
|
||||||
|
const homeCard = x => {
|
||||||
|
const done = histClass(x) === "done", hold = histClass(x) === "hold";
|
||||||
|
const c = el("a", "card" + (hold ? " hold" : ""));
|
||||||
|
c.href = histHref(x);
|
||||||
|
if (x.poster) {
|
||||||
|
const i = el("img"); i.src = x.poster; i.alt = "";
|
||||||
|
c.append(i);
|
||||||
|
}
|
||||||
|
let meta = `E${x.ep} \u00b7 ${x.lang === "eng" ? "dub" : "sub"}`;
|
||||||
|
if (done) meta += " \u00b7 \u2713";
|
||||||
|
else if (x.dur > 0 && x.pos < x.dur * 0.95) meta += ` \u00b7 ${fmtMin((x.dur - x.pos) / 60)} left`;
|
||||||
|
else if (hold) meta += ` \u00b7 ${Math.round(Date.now() / 1000 / 86400 - x.t / 86400)}d ago`;
|
||||||
|
const info = el("div", "mini-info");
|
||||||
|
info.append(el("div", "card-name", x.title), el("div", "mini-meta", meta));
|
||||||
|
const pb = bar(x, "small");
|
||||||
|
if (pb) info.append(pb);
|
||||||
|
c.append(info);
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
const showSec = (title, items, extra) => {
|
||||||
|
if (!items.length) return;
|
||||||
|
const head = el("div", "sec-head");
|
||||||
|
head.append(el("h2", null, title));
|
||||||
|
for (const a of extra) head.append(a);
|
||||||
|
const grid = el("div", "grid mini");
|
||||||
|
for (const x of items) grid.append(homeCard(x));
|
||||||
|
main.append(head, grid);
|
||||||
|
};
|
||||||
|
const link = (text, href) => Object.assign(el("a", null, text), {href: BASE + href});
|
||||||
|
|
||||||
function showHome() {
|
function showHome() {
|
||||||
main.innerHTML = "";
|
main.innerHTML = "";
|
||||||
if (!history.length) { main.append(el("p", "muted", "Search for an anime to get started.")); return; }
|
if (!history.length) {
|
||||||
|
main.append(el("p", "muted empty", "Search for an anime to get started."));
|
||||||
|
api("/api/searches").then(rs => {
|
||||||
|
if (!rs.length || !main.querySelector("p.empty")) return;
|
||||||
|
const d = el("div", null);
|
||||||
|
d.append(el("div", "muted", "recent searches"));
|
||||||
|
const chips = el("div", "chips");
|
||||||
|
for (const q of rs) chips.append(link(q, "?q=" + encodeURIComponent(q)));
|
||||||
|
d.append(chips);
|
||||||
|
main.append(d);
|
||||||
|
}).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const hero = history[0];
|
const hero = history[0];
|
||||||
// 1. continue-watching hero
|
// 1. continue-watching hero
|
||||||
const sec = el("section", "hero-sec");
|
const sec = el("section", "hero-sec");
|
||||||
@@ -107,44 +157,21 @@ function showHome() {
|
|||||||
` · ${timeago(hero.t)}`));
|
` · ${timeago(hero.t)}`));
|
||||||
const p = bar(hero);
|
const p = bar(hero);
|
||||||
if (p) body.append(p);
|
if (p) body.append(p);
|
||||||
const go = el("button", "btn resume", "▶ Resume");
|
const heroDone = hero.dur > 0 && hero.pos >= hero.dur * 0.95;
|
||||||
|
const go = el("button", "btn resume", heroDone ? "\u25b6 Next ep" : "\u25b6 Resume");
|
||||||
go.onclick = () => location.href = histHref(hero);
|
go.onclick = () => location.href = histHref(hero);
|
||||||
right.append(body, go);
|
right.append(body, go);
|
||||||
sec.append(card, right);
|
sec.append(card, right);
|
||||||
main.append(sec);
|
main.append(sec);
|
||||||
// 2-4. the rest as mini poster cards with progress + time-ago
|
// 2-4. status board: one card per anime, grouped by state
|
||||||
if (history.length > 1) {
|
const shows = byShow().filter(x => x !== hero);
|
||||||
const head = el("div", "sec-head");
|
const cont = shows.filter(x => histClass(x) === "watching").slice(0, 6);
|
||||||
head.append(el("h2", null, "Recently played"));
|
const hold = shows.filter(x => histClass(x) === "hold").slice(0, 4);
|
||||||
const st = el("a", null, "stats \u2192");
|
const done = shows.filter(x => histClass(x) === "done").slice(0, 6);
|
||||||
st.href = BASE + "?stats";
|
showSec("Continue watching", cont, [link("stats \u2192", "?stats"),
|
||||||
head.append(st);
|
link("history \u2192", "?history")]);
|
||||||
if (history.length >= 5) {
|
showSec("On hold", hold, hold.length ? [link("history \u2192", "?history")] : []);
|
||||||
const va = el("a", null, "view all \u2192");
|
showSec("Recently finished", done, done.length ? [link("history \u2192", "?history")] : []);
|
||||||
va.href = BASE + "?history";
|
|
||||||
head.append(va);
|
|
||||||
}
|
|
||||||
main.append(head);
|
|
||||||
const grid = el("div", "grid mini");
|
|
||||||
for (const x of history.slice(1, 6)) {
|
|
||||||
const c = el("a", "card");
|
|
||||||
c.href = histHref(x);
|
|
||||||
if (x.poster) {
|
|
||||||
const i = el("img"); i.src = x.poster; i.alt = "";
|
|
||||||
c.append(i);
|
|
||||||
}
|
|
||||||
const info = el("div", "mini-info");
|
|
||||||
info.append(el("div", "card-name", x.title));
|
|
||||||
const meta = el("div", "mini-meta");
|
|
||||||
meta.textContent = `E${x.ep} · ${x.lang === "eng" ? "dub" : "sub"} · ${timeago(x.t)}`;
|
|
||||||
info.append(meta);
|
|
||||||
const pb = bar(x, "small");
|
|
||||||
if (pb) info.append(pb);
|
|
||||||
c.append(info);
|
|
||||||
grid.append(c);
|
|
||||||
}
|
|
||||||
main.append(grid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- search ----------
|
// ---------- search ----------
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ header form { flex: 1; max-width: 420px; }
|
|||||||
button.tiny, a.tiny { padding: .2rem .55rem; font-size: .8rem; border-radius: 6px;
|
button.tiny, a.tiny { padding: .2rem .55rem; font-size: .8rem; border-radius: 6px;
|
||||||
border: 1px solid var(--bd); background: var(--card); color: var(--fg); cursor: pointer; }
|
border: 1px solid var(--bd); background: var(--card); color: var(--fg); cursor: pointer; }
|
||||||
a.tiny:hover { border-color: var(--acc); }
|
a.tiny:hover { border-color: var(--acc); }
|
||||||
|
a.chip:hover { border-color: var(--acc); }
|
||||||
button.tiny.danger { border-color: #7f1d1d; color: #f87171; }
|
button.tiny.danger { border-color: #7f1d1d; color: #f87171; }
|
||||||
#menubox { position: relative; }
|
#menubox { position: relative; }
|
||||||
#menu-items { display: none; position: absolute; right: 0; top: calc(100% + 4px);
|
#menu-items { display: none; position: absolute; right: 0; top: calc(100% + 4px);
|
||||||
|
|||||||
Reference in new issue
Block a user