diff --git a/package.json b/package.json index 7d18b41..8d6e543 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,9 @@ "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-unicorn": "^56.0.1", + "eslint-plugin-unicorn": "^58.0.0", "eslint-plugin-unused-imports": "^4.1.4", - "globals": "^15.15.0", + "globals": "^16.0.0", "prettier": "^3.5.3" }, "peerDependencies": { @@ -29,7 +29,8 @@ }, "dependencies": { "ejs": "^3.1.10", - "node-vibrant": "^4.0.3", - "marked": "^15.0.7" + "isomorphic-dompurify": "^2.23.0", + "marked": "^15.0.7", + "node-vibrant": "^4.0.3" } } diff --git a/public/css/index.css b/public/css/index.css index da470b9..e4acc03 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -142,18 +142,28 @@ ul { display: flex; flex-direction: row; gap: 1rem; - background: #1a1a1d; - padding: 1rem; - border-radius: 6px; - box-shadow: 0 0 0 1px #2e2e30; - transition: background 0.2s ease; - align-items: flex-start; + background-color: #1e1f22; + padding: 0.75rem 1rem; + border-radius: 10px; + box-shadow: 0 1px 0 0 #2e2e30; } .activity:hover { background: #2a2a2d; } +.activity-wrapper { + display: flex; + flex-direction: column; + width: 100%; +} + +.activity-wrapper-inner { + display: flex; + flex-direction: row; + gap: 1rem; +} + .activity-art { width: 80px; height: 80px; @@ -165,7 +175,15 @@ ul { .activity-content { display: flex; flex-direction: column; + justify-content: space-between; flex: 1; + gap: 0.5rem; + position: relative; +} + +.activity-top { + display: flex; + flex-direction: column; gap: 0.25rem; } @@ -175,36 +193,49 @@ ul { align-items: flex-start; } +.activity-bottom { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + .activity-name { - font-weight: bold; - font-size: 1.1rem; - color: #ffffff; + font-weight: 600; + font-size: 1rem; + color: #fff; } .activity-detail { - font-size: 0.95rem; - color: #ccc; + font-size: 0.875rem; + color: #b5bac1; } .activity-timestamp { - font-size: 0.8rem; - color: #777; + font-size: 0.75rem; + color: #b5bac1; text-align: right; + margin-left: auto; + white-space: nowrap; } .progress-bar { - height: 6px; - background-color: #333; - border-radius: 3px; - overflow: hidden; - width: 100%; + height: 4px; + background-color: #2e2e30; + border-radius: 2px; margin-top: 0.5rem; + overflow: hidden; } .progress-fill { + background-color: #5865f2; + transition: width 0.4s ease; height: 100%; - background-color: #00b0f4; - transition: width 0.5s ease; +} + +.progress-bar, +.progress-time-labels { + width: 100%; + margin-top: 0.5rem; } .progress-time-labels { @@ -215,6 +246,21 @@ ul { margin-top: 0.25rem; } +.activity-type-wrapper { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.activity-type-label { + font-size: 0.75rem; + text-transform: uppercase; + font-weight: 600; + color: #aaa; + margin-bottom: 0.50rem; + display: block; +} + .activity-header.no-timestamp { justify-content: flex-start; } @@ -226,9 +272,9 @@ ul { .activity-buttons { display: flex; - flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; + justify-content: flex-end; } .activity-button { @@ -249,10 +295,9 @@ ul { text-decoration: none; } -.activity-button.disabled { - background-color: #4e5058; - cursor: default; - pointer-events: none; +.activity-button:disabled { + background-color: #2d2e31; + cursor: not-allowed; opacity: 0.8; } @@ -392,6 +437,8 @@ ul { border-radius: 8px; box-shadow: 0 0 0 1px #2e2e30; + margin-top: 2rem; + box-sizing: border-box; overflow: hidden; overflow-y: auto; diff --git a/public/js/index.js b/public/js/index.js index 6feec74..8b40b41 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -4,9 +4,20 @@ const activityProgressMap = new Map(); function formatTime(ms) { const totalSecs = Math.floor(ms / 1000); - const mins = Math.floor(totalSecs / 60); + const hours = Math.floor(totalSecs / 3600); + const mins = Math.floor((totalSecs % 3600) / 60); const secs = totalSecs % 60; - return `${mins}:${secs.toString().padStart(2, "0")}`; + + return `${String(hours).padStart(1, "0")}:${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`; +} + +function formatVerbose(ms) { + const totalSecs = Math.floor(ms / 1000); + const hours = Math.floor(totalSecs / 3600); + const mins = Math.floor((totalSecs % 3600) / 60); + const secs = totalSecs % 60; + + return `${hours}h ${mins}m ${secs}s`; } function updateElapsedAndProgress() { @@ -17,17 +28,14 @@ function updateElapsedAndProgress() { if (!start) return; const elapsed = now - start; - const mins = Math.floor(elapsed / 60000); - const secs = Math.floor((elapsed % 60000) / 1000); const display = el.querySelector(".elapsed"); - if (display) - display.textContent = `(${mins}m ${secs.toString().padStart(2, "0")}s ago)`; + if (display) display.textContent = `(${formatVerbose(elapsed)} ago)`; }); document.querySelectorAll(".progress-bar").forEach((bar) => { const start = Number(bar.dataset.start); const end = Number(bar.dataset.end); - if (!start || !end || end <= start) return; + if (!start || !end || end <= start || now > end) return; const duration = end - start; const elapsed = now - start; @@ -43,7 +51,7 @@ function updateElapsedAndProgress() { document.querySelectorAll(".progress-time-labels").forEach((label) => { const start = Number(label.dataset.start); const end = Number(label.dataset.end); - if (!start || !end || end <= start) return; + if (!start || !end || end <= start || now > end) return; const current = Math.max(0, now - start); const total = end - start; @@ -102,7 +110,7 @@ if (userId && instanceUri) { if (payload.t === "INIT_STATE" || payload.t === "PRESENCE_UPDATE") { updatePresence(payload.d); - updateElapsedAndProgress(); + requestAnimationFrame(() => updateElapsedAndProgress()); } }); } @@ -129,64 +137,98 @@ function buildActivityHTML(activity) { art = `https://cdn.discordapp.com/app-assets/${activity.application_id}/${img}.png`; } + const activityTypeMap = { + 0: "Playing", + 1: "Streaming", + 2: "Listening", + 3: "Watching", + 4: "Custom Status", + 5: "Competing", + }; + + const activityType = + activity.name === "Spotify" + ? "Listening to Spotify" + : activity.name === "TIDAL" + ? "Listening to TIDAL" + : activityTypeMap[activity.type] || "Playing"; + const activityTimestamp = - !total && start - ? ` -
` + start && progress === null + ? ` ` + : ""; + + const activityButtons = + activity.buttons && activity.buttons.length > 0 + ? ` ` : ""; const progressBar = progress !== null - ? ` - ` : ""; - const activityButtons = activity.buttons && activity.buttons.length > 0 - ? ` ` - : ''; + const isMusic = activity.type === 2 || activity.type === 3; + + const primaryLine = isMusic ? activity.details : activity.name; + const secondaryLine = isMusic ? activity.state : activity.details; + const tertiaryLine = isMusic ? activity.assets?.large_text : activity.state; return `