diff --git a/README.md b/README.md index 438bf0c..2ab336e 100644 --- a/README.md +++ b/README.md @@ -269,9 +269,13 @@ in three kinds: date) and one group per `recent` widget (newest first) - each group is a header (title, refresh stamp, per-widget errors) followed by one card per release, laid out as a wrapping grid - then notices, urgent first, then heads-up then - info, each group in creation order. The next upcoming release that carries - artwork also gets a full-width hero tile above the groups (its relayed thumb - plus title and date). Each widget gets its own sidebar button for a + info, each group in creation order. Cards are tinted by the app that produced + them (Sonarr teal, Radarr violet, Plex orange - separate light/dark shades of + each), on the card edge, the source chip, and a legend chip per origin in the + group header, so a merged timeline says which app each card came from. The + next upcoming release that carries artwork also gets a full-width hero tile + above the groups (its relayed thumb plus title and date), ringed in the same + origin hue. Each widget gets its own sidebar button for a single-widget view. - Writes are admin-only when OIDC is on, reads any session - same gate as services. diff --git a/src/uwudash_mk3/static/app.js b/src/uwudash_mk3/static/app.js index 2e33d9e..b3d4a8b 100644 --- a/src/uwudash_mk3/static/app.js +++ b/src/uwudash_mk3/static/app.js @@ -233,12 +233,20 @@ function noticeCard(widget) { ); } +// Cards are tinted by the app that produced them, so a merged timeline reads by +// source at a glance; an unknown origin stays neutral. +const ORIGIN_LABEL = { sonarr: "Sonarr", radarr: "Radarr", plex: "Plex" }; + +function originClass(item) { + return ORIGIN_LABEL[item ? item.origin : null] ? ` origin-${item.origin}` : ""; +} + function releaseCard(item) { const thumb = item.thumb ? `${esc(item.label)}` : ""; return ( - `
` + + `
` + thumb + `${esc(item.label)}` + (item.sub ? `${esc(item.sub)}` : "") + @@ -286,12 +294,17 @@ function liveCard(widgets, title, ascending) { const refreshed = widgets.map((widget) => (widget.data || {}).fetched_at || "").filter(Boolean); const stamp = refreshed.length ? `refreshed ${clockOf(refreshed.sort()[0])}` : ""; const meta = [...notes, stamp].filter(Boolean).join(" · "); + const legend = Object.keys(ORIGIN_LABEL) + .filter((key) => rows.some((row) => row.origin === key)) + .map((key) => `${ORIGIN_LABEL[key]}`) + .join(""); return ( `
` + `
` + `${widgetIcon(widgets[0])}` + `
${esc(title)}
` + `
${esc(meta)}
` + + (legend ? `
${legend}
` : "") + `
` + (rows.length ? `
${rows.map(releaseCard).join("")}
` : "") + (rows.length ? "" : `
${esc(meta || "nothing in the window")}
`) + @@ -324,7 +337,7 @@ function heroTile(widgets) { .sort((a, b) => (a.date < b.date ? -1 : 1)); const next = upcoming.length ? upcoming[0] : thumbnailed[0]; return ( - `
` + + `
` + `${esc(next.label)}` + `
` + `${esc(next.label)}` + diff --git a/src/uwudash_mk3/static/styles.css b/src/uwudash_mk3/static/styles.css index f5e93bb..b216383 100644 --- a/src/uwudash_mk3/static/styles.css +++ b/src/uwudash_mk3/static/styles.css @@ -9,6 +9,10 @@ --success: #3fbf7f; --error: #e0556b; --warning: #d9a441; + /* release cards are tinted by the app that produced them */ + --origin-sonarr: #45c8c2; + --origin-radarr: #a97ce8; + --origin-plex: #f2813f; } @media (prefers-color-scheme: light) { @@ -20,6 +24,9 @@ --fg: #16202e; --muted: #61708a; --accent: #2f6fd0; + --origin-sonarr: #127c78; + --origin-radarr: #6b3fa8; + --origin-plex: #b3541f; } } @@ -363,6 +370,31 @@ body { .release-card .widget-sub { color: var(--muted); font-size: 12px; } .release-card .widget-when { font-size: 12px; font-weight: 700; color: var(--muted); } .release-card .widget-src { font-size: 11px; color: var(--accent); } + +/* origin tints: Sonarr teal, Radarr violet, Plex orange. The merged timeline + mixes apps, so the card edge, the source chip and a legend chip in the group + header all carry the same hue. */ +.release-card.origin-sonarr { border-left: 3px solid var(--origin-sonarr); } +.release-card.origin-radarr { border-left: 3px solid var(--origin-radarr); } +.release-card.origin-plex { border-left: 3px solid var(--origin-plex); } +.release-card.origin-sonarr .widget-src { color: var(--origin-sonarr); } +.release-card.origin-radarr .widget-src { color: var(--origin-radarr); } +.release-card.origin-plex .widget-src { color: var(--origin-plex); } +.live-head .widget-keys { display: flex; flex-wrap: wrap; gap: 8px; } +.origin-key { + font-size: 11px; + font-weight: 600; + padding: 1px 7px; + border-radius: 999px; + border: 1px solid currentColor; + color: var(--muted); +} +.origin-key.origin-sonarr { color: var(--origin-sonarr); } +.origin-key.origin-radarr { color: var(--origin-radarr); } +.origin-key.origin-plex { color: var(--origin-plex); } +.home-hero.origin-sonarr { border: 2px solid var(--origin-sonarr); } +.home-hero.origin-radarr { border: 2px solid var(--origin-radarr); } +.home-hero.origin-plex { border: 2px solid var(--origin-plex); } .widget-empty { color: var(--muted); font-size: 13px; } .home-empty { color: var(--muted); font-size: 14px; } diff --git a/src/uwudash_mk3/widgets.py b/src/uwudash_mk3/widgets.py index 48cf329..b31e11c 100644 --- a/src/uwudash_mk3/widgets.py +++ b/src/uwudash_mk3/widgets.py @@ -275,6 +275,7 @@ class WidgetData: "label": show or episode_title or "?", "sub": " · ".join(bits), "type": "episode", + "origin": "sonarr", "source": widget.name, "thumb": None, } @@ -299,6 +300,7 @@ class WidgetData: "label": label, "sub": RADARR_DATE_LABELS.get(field, ""), "type": "movie", + "origin": "radarr", "source": widget.name, "thumb": self._thumb(widget, self._radarr_image(item)), } @@ -333,6 +335,7 @@ class WidgetData: "label": str(item.get("title") or "?"), "sub": " · ".join(bits), "type": str(item.get("type") or "added"), + "origin": "plex", "source": widget.name, "thumb": self._thumb(widget, item.get("thumb")), }