diff --git a/app.py b/app.py index 982f9a9..ebf91b5 100644 --- a/app.py +++ b/app.py @@ -1,9 +1,11 @@ from flask import Flask, render_template from utils.listenbrainz import listenbrainz_bp +from utils.steam import steam_bp app = Flask(__name__) app.register_blueprint(listenbrainz_bp) +app.register_blueprint(steam_bp) @app.route("/") def index(): diff --git a/static/css/style.css b/static/css/style.css index a1bd1f9..0f87e88 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1041,6 +1041,62 @@ a.ym-now-link:hover .ym-track-title { color: var(--accent); } .ym-link { padding: 9px 11px; gap: 10px; } } +/* ---------- активность: музыка слева, Steam справа ---------- */ +.activity-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 26px; + align-items: start; +} + +.activity-grid .ym-tracker { + max-width: none; + margin: 0; + padding: 0; +} + +.steam-panel { min-width: 0; } + +.st-sub { + display: flex; + align-items: center; + gap: 12px; + margin: 18px 0 10px; +} + +.st-sub h3 { + font-family: var(--mono); + font-size: .74rem; + font-weight: 600; + letter-spacing: .12em; + text-transform: uppercase; + color: var(--muted); + white-space: nowrap; +} + +.st-sub::after { + content: ""; + flex: 1; + height: 1px; + background: rgba(255, 255, 255, .07); +} + +.st-foot { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px 14px; + margin-top: 16px; + font-family: var(--mono); + font-size: .76rem; + color: var(--muted); +} + +@media (max-width: 900px) { + .activity-grid { grid-template-columns: 1fr; } +} + /* ---------- кнопка друга ---------- */ .friend-cta { display: flex; diff --git a/static/js/steam.js b/static/js/steam.js new file mode 100644 index 0000000..e3296f2 --- /dev/null +++ b/static/js/steam.js @@ -0,0 +1,138 @@ +(() => { + "use strict"; + + const recentList = document.getElementById("steamRecent"); + const topList = document.getElementById("steamTop"); + const updated = document.getElementById("steamUpdated"); + const refresh = document.getElementById("steamRefresh"); + const countEl = document.getElementById("steamCount"); + if (!recentList || !topList || !updated) return; + + const SOURCE = "/api/steam/activity"; + const POLL_MS = 60000; + + let timer = null; + let busy = false; + + const escapeHtml = (s) => + String(s).replace(/[&<>"']/g, (c) => ({ + "&": "&", "<": "<", ">": ">", '"': """, "'": "'", + })[c]); + + const IMG_FALLBACK = "data:image/svg+xml," + encodeURIComponent( + "" + ); + + const fmtHours = (h) => { + if (h == null) return ""; + if (h >= 168) return `${(h / 24).toFixed(1)} д`; + if (h >= 1) return `${h.toFixed(1)} ч`; + return `${Math.round(h * 60)} мин`; + }; + + const fmtDate = (ts) => { + if (!ts) return ""; + const d = new Date(ts * 1000); + const today = new Date(); + today.setHours(0, 0, 0, 0); + if (d >= today) return "сегодня"; + if (d >= new Date(today.getTime() - 86400000)) return "вчера"; + return d.toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit", year: "numeric" }); + }; + + const bindCovers = (root) => { + root.querySelectorAll("img.st-cover").forEach((img) => { + img.addEventListener("error", () => { img.src = IMG_FALLBACK; }, { once: true }); + }); + }; + + const rowHtml = (g, right, sub) => `