From 3ff50a993241313d58db8713bb758a1d00e7c8e5 Mon Sep 17 00:00:00 2001 From: ctrlcat0x Date: Sat, 15 Nov 2025 00:44:54 +0530 Subject: [PATCH] feat: update download group UI with hero section and speed chart integration --- .../src/pages/downloads/download-group.scss | 422 ++++++----- .../src/pages/downloads/download-group.tsx | 657 ++++++++++++------ .../src/pages/downloads/downloads.scss | 1 - 3 files changed, 683 insertions(+), 397 deletions(-) diff --git a/src/renderer/src/pages/downloads/download-group.scss b/src/renderer/src/pages/downloads/download-group.scss index 22bff527..e8549ef3 100644 --- a/src/renderer/src/pages/downloads/download-group.scss +++ b/src/renderer/src/pages/downloads/download-group.scss @@ -4,11 +4,13 @@ display: flex; flex-direction: column; gap: calc(globals.$spacing-unit * 2); + margin-inline: calc(globals.$spacing-unit * 3); &__header { display: flex; align-items: center; justify-content: space-between; + padding-top: calc(globals.$spacing-unit * 4); gap: calc(globals.$spacing-unit * 2); &-divider { @@ -21,164 +23,180 @@ font-weight: 400; } } - - &__downloads { + &--hero { width: 100%; - gap: calc(globals.$spacing-unit * 3); - display: flex; - flex-direction: column; + position: relative; + overflow: hidden; margin: 0; padding: 0; - margin-top: globals.$spacing-unit; + margin-bottom: calc(globals.$spacing-unit * 3); } - &__item { - width: 100%; - background-color: globals.$background-color; - display: flex; - border-radius: 8px; - border: solid 1px globals.$border-color; - overflow: hidden; - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.5); - transition: all ease 0.2s; - height: 250px; - min-height: 250px; - max-height: 250px; - position: relative; - - &:before { - content: ""; - top: 0; - left: 0; - width: 100%; - height: 172%; - position: absolute; - background: linear-gradient( - 35deg, - rgba(0, 0, 0, 0.1) 0%, - rgba(0, 0, 0, 0.07) 51.5%, - rgba(255, 255, 255, 0.15) 64%, - rgba(255, 255, 255, 0.1) 100% - ); - transition: all ease 0.3s; - transform: translateY(-36%); - opacity: 0.5; - z-index: 1; - } - - &:hover { - transform: scale(1.01); - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); - border-color: rgba(255, 255, 255, 0.1); - } - - &:hover::before { - opacity: 1; - transform: translateY(-20%); - } - - &--hydra { - box-shadow: 0px 0px 16px 0px rgba(12, 241, 202, 0.15); - } - } - - &__background-image { - width: 100%; - height: 100%; + &__hero-background { position: absolute; top: 0; left: 0; + width: 100%; + height: 120%; z-index: 0; img { width: 100%; height: 100%; object-fit: cover; - object-position: 50% 25%; + object-position: 50% 20%; } } - - &__background-overlay { + // PLEASE FIX THE COLORS + &__hero-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient( - 130deg, - rgba(0, 0, 0, 0.2) 0%, - rgba(0, 0, 0, 0.5) 50%, - rgba(0, 0, 0, 0.8) 100% + to bottom, + rgba(0, 0, 0, 0.3) 0%, + rgba(0, 0, 0, 1) 70%, + rgb(27, 27, 27) 100% ); } - &__content { + &__hero-content { position: relative; - z-index: 2; - width: 100%; - height: 100%; - display: flex; - } - - &__left-section { - flex: 1; - max-width: 50%; - height: 100%; - display: flex; - align-items: flex-end; - padding: calc(globals.$spacing-unit * 2); - } - - &__logo-container { + z-index: 1; + padding: calc(globals.$spacing-unit * 4); + padding-bottom: 0; display: flex; flex-direction: column; - gap: globals.$spacing-unit; - } - - &__logo { - max-width: 350px; - max-height: 150px; - object-fit: contain; - filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.8)); - } - - &__game-title { - font-size: 24px; - font-weight: 700; - color: #ffffff; - text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9); - margin: 0; - } - - &__downloader-badge { - align-self: flex-start; - } - - &__right-section { - flex: 1; - max-width: 50%; - display: flex; - flex-direction: column; - padding: calc(globals.$spacing-unit * 2); - position: relative; - justify-content: space-between; - } - - &__top-row { - display: flex; - align-items: center; - justify-content: space-between; gap: calc(globals.$spacing-unit * 2); } - &__stats { + &__hero-header { display: flex; - gap: calc(globals.$spacing-unit * 3); + justify-content: flex-end; + margin-bottom: calc(globals.$spacing-unit * 2); } - &__stat { + &__hero-logo { + flex: 1; + + img { + max-width: 600px; + max-height: 200px; + object-fit: contain; + filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.8)); + } + + h1 { + font-size: 64px; + font-weight: 700; + color: #ffffff; + text-shadow: 2px 2px 12px rgba(0, 0, 0, 0.9); + margin: 0; + } + } + + &__hero-actions { + display: flex; + gap: calc(globals.$spacing-unit); + align-items: center; + } + + &__hero-action-row { + display: flex; + justify-content: space-between; + align-items: flex-end; + gap: calc(globals.$spacing-unit * 3); + margin-bottom: calc(globals.$spacing-unit * 3); + } + + &__hero-menu-btn { + background-color: rgba(0, 0, 0, 0.4); + padding: calc(globals.$spacing-unit); + } + + &__hero-progress { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit); + margin-bottom: calc(globals.$spacing-unit * 3); + } + + &__progress-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: calc(globals.$spacing-unit / 2); + } + + &__progress-status { + font-size: 13px; + font-weight: 600; + color: rgba(255, 255, 255, 0.9); + text-transform: uppercase; + letter-spacing: 0.5px; + } + + &__progress-percentage { + font-size: 14px; + font-weight: 700; + color: #ffffff; + } + + &__progress-details { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 13px; + color: rgba(255, 255, 255, 0.9); + margin-top: calc(globals.$spacing-unit / 2); + } + + &__progress-size { + font-weight: 600; + } + + &__progress-time { + color: globals.$muted-color; + } + + &__hero-stats { + display: flex; + gap: calc(globals.$spacing-unit * 4); + padding: calc(globals.$spacing-unit * 2); + border-radius: 12px; + background: rgba(26, 26, 26, 0.1); + backdrop-filter: blur(8px); + margin-top: calc(globals.$spacing-unit * 2); + } + + &__stats-column { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 2); + min-width: 200px; + padding-right: calc(globals.$spacing-unit * 2); + border-right: 1px solid rgba(255, 255, 255, 0.1); + } + + &__speed-chart { + flex: 1; display: flex; align-items: center; + justify-content: center; + overflow: hidden; + } + + &__speed-chart-canvas { + width: 100%; + height: 100px; + image-rendering: crisp-edges; + } + + &__stat-item { + display: flex; + align-items: flex-end; gap: calc(globals.$spacing-unit); svg { @@ -187,9 +205,8 @@ } } - &__stat-info { + &__stat-content { display: flex; - flex-direction: column; gap: 2px; } @@ -199,108 +216,135 @@ letter-spacing: 0.5px; font-size: 10px; color: rgba(255, 255, 255, 0.6); - line-height: 1; } &__stat-value { color: #ffffff; font-weight: 700; - font-size: 14px; + font-size: 11px; line-height: 1.2; } - &__menu-button { - border: none; - padding: 8px; - min-height: unset; - background-color: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(4px); - flex-shrink: 0; - - &:hover { - background-color: rgba(0, 0, 0, 0.8); - } - } - - &__progress-section { + &__simple-list { + width: 100%; display: flex; flex-direction: column; - gap: calc(globals.$spacing-unit / 2); - flex: 1; + gap: calc(globals.$spacing-unit * 2); + margin: 0; + padding: 0; + list-style: none; } - &__bottom-row { + &__simple-card { display: flex; align-items: center; gap: calc(globals.$spacing-unit * 2); + padding: calc(globals.$spacing-unit * 2); + border-radius: 8px; + transition: all ease 0.2s; + + &:hover { + background-color: rgba(255, 255, 255, 0.02); + border-color: rgba(255, 255, 255, 0.1); + } } - &__progress-info { + &__simple-thumbnail { + width: 200px; + height: 100px; + border-radius: 6px; + overflow: hidden; + flex-shrink: 0; + background-color: rgba(0, 0, 0, 0.3); + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + &__simple-info { + flex: 1; + min-width: 0; display: flex; - justify-content: space-between; - font-size: 12px; - color: rgba(255, 255, 255, 0.8); + flex-direction: column; + gap: calc(globals.$spacing-unit / 2); } - &__progress-text { + &__simple-title { + font-size: 16px; font-weight: 600; + color: #ffffff; + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } - &__progress-size { + &__simple-meta { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 2); + font-size: 13px; color: globals.$muted-color; } + &__simple-size { + font-weight: 500; + } + + &__simple-seeding { + color: #4ade80; + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + &__simple-progress { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit / 2); + width: 200px; + flex-shrink: 0; + } + + &__simple-progress-text { + font-size: 12px; + font-weight: 600; + color: rgba(255, 255, 255, 0.8); + text-align: right; + } + + &__simple-actions { + flex-shrink: 0; + } + + &__simple-menu-btn { + padding: calc(globals.$spacing-unit); + min-height: unset; + } + &__progress-bar { width: 100%; - height: 6px; + height: 8px; background-color: rgba(255, 255, 255, 0.08); border-radius: 4px; overflow: hidden; position: relative; + + &--small { + height: 6px; + } } &__progress-fill { height: 100%; background-color: globals.$muted-color; - transition: width 0.3s ease; + transition: + width 0.3s ease, + background 0.35s ease; border-radius: 4px; } - - &__time-remaining { - font-size: 11px; - color: globals.$muted-color; - text-align: left; - min-height: 16px; - } - - &__quick-actions { - display: flex; - flex-shrink: 0; - min-height: 40px; - align-items: center; - } - - &__action-btn { - display: flex; - align-items: center; - gap: calc(globals.$spacing-unit / 2); - padding: calc(globals.$spacing-unit) calc(globals.$spacing-unit * 2); - font-size: 13px; - font-weight: 600; - - svg { - width: 14px; - height: 14px; - } - } - - &__hydra-gradient { - background: linear-gradient(90deg, #01483c 0%, #0cf1ca 50%, #01483c 100%); - box-shadow: 0px 0px 8px 0px rgba(12, 241, 202, 0.15); - width: 100%; - position: absolute; - bottom: 0; - height: 2px; - z-index: 2; - } } diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 9f999317..64cf5803 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -1,5 +1,3 @@ -import cn from "classnames"; - import type { GameShop, LibraryGame, SeedingStatus } from "@types"; import { Badge, Button } from "@renderer/components"; @@ -12,7 +10,7 @@ import { useAppSelector, useDownload, useLibrary } from "@renderer/hooks"; import "./download-group.scss"; import { useTranslation } from "react-i18next"; -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { DropdownMenu, DropdownMenuItem, @@ -27,9 +25,99 @@ import { TrashIcon, UnlinkIcon, XCircleIcon, - DatabaseIcon, GraphIcon, } from "@primer/octicons-react"; +import { average } from "color.js"; + +interface SpeedChartProps { + speeds: number[]; + peakSpeed: number; + color?: string; +} + +function SpeedChart({ + speeds, + peakSpeed, + color = "rgba(255, 255, 255, 1)", +}: SpeedChartProps) { + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const width = canvas.width; + const height = canvas.height; + const totalBars = 120; + const barWidth = 4; + const barGap = 10; + const barSpacing = barWidth + barGap; + const maxHeight = peakSpeed || Math.max(...speeds, 1); + + ctx.clearRect(0, 0, width, height); + + for (let i = 0; i < totalBars; i++) { + const x = i * barSpacing; + + ctx.fillStyle = "rgba(255, 255, 255, 0.08)"; + ctx.beginPath(); + ctx.roundRect(x, 0, barWidth, height, 3); + ctx.fill(); + + if (i < speeds.length) { + const speed = speeds[i] || 0; + const filledHeight = (speed / maxHeight) * height; + + if (filledHeight > 0) { + const gradient = ctx.createLinearGradient( + 0, + height - filledHeight, + 0, + height + ); + + let r = 8, + g = 234, + b = 121; + + if (color.startsWith("#")) { + const hex = color.replace("#", ""); + r = parseInt(hex.substring(0, 2), 16); + g = parseInt(hex.substring(2, 4), 16); + b = parseInt(hex.substring(4, 6), 16); + } else if (color.startsWith("rgb")) { + const matches = color.match(/\d+/g); + if (matches && matches.length >= 3) { + r = parseInt(matches[0]); + g = parseInt(matches[1]); + b = parseInt(matches[2]); + } + } + + gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 1)`); + gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0.7)`); + + ctx.fillStyle = gradient; + ctx.beginPath(); + ctx.roundRect(x, height - filledHeight, barWidth, filledHeight, 3); + ctx.fill(); + } + } + } + }, [speeds, peakSpeed, color]); + + return ( + + ); +} export interface DownloadGroupProps { library: LibraryGame[]; @@ -65,14 +153,98 @@ export function DownloadGroup({ } = useDownload(); const peakSpeedsRef = useRef>({}); + const speedHistoryRef = useRef>({}); + const [dominantColors, setDominantColors] = useState>( + {} + ); + + const extractDominantColor = useCallback( + async (imageUrl: string, gameId: string) => { + if (dominantColors[gameId]) return; + + try { + const color = await average(imageUrl, { amount: 1, format: "hex" }); + const colorString = + typeof color === "string" ? color : color.toString(); + setDominantColors((prev) => ({ ...prev, [gameId]: colorString })); + } catch (error) { + console.error("Failed to extract dominant color:", error); + } + }, + [dominantColors] + ); + useEffect(() => { - if (lastPacket?.gameId && lastPacket.downloadSpeed) { - const currentPeak = peakSpeedsRef.current[lastPacket.gameId] || 0; + if (lastPacket?.gameId && lastPacket.downloadSpeed !== undefined) { + const gameId = lastPacket.gameId; + + const currentPeak = peakSpeedsRef.current[gameId] || 0; if (lastPacket.downloadSpeed > currentPeak) { - peakSpeedsRef.current[lastPacket.gameId] = lastPacket.downloadSpeed; + peakSpeedsRef.current[gameId] = lastPacket.downloadSpeed; + } + + if (!speedHistoryRef.current[gameId]) { + speedHistoryRef.current[gameId] = []; + } + + speedHistoryRef.current[gameId].push(lastPacket.downloadSpeed); + + if (speedHistoryRef.current[gameId].length > 60) { + speedHistoryRef.current[gameId].shift(); } } }, [lastPacket?.gameId, lastPacket?.downloadSpeed]); + + useEffect(() => { + library.forEach((game) => { + if ( + game.download && + game.download.progress < 0.01 && + game.download.status !== "paused" + ) { + // Fresh download - clear any old data + if (speedHistoryRef.current[game.id]?.length > 0) { + speedHistoryRef.current[game.id] = []; + peakSpeedsRef.current[game.id] = 0; + } + } + }); + }, [library]); + + useEffect(() => { + const cleanupIntervals: NodeJS.Timeout[] = []; + + library.forEach((game) => { + if (game.download?.progress === 1 || !game.download) { + if (speedHistoryRef.current[game.id]?.length > 0) { + const interval = setInterval(() => { + if (speedHistoryRef.current[game.id]?.length > 0) { + speedHistoryRef.current[game.id].shift(); + } else { + clearInterval(interval); + } + }, 50); + cleanupIntervals.push(interval); + } + } + }); + + return () => { + cleanupIntervals.forEach((interval) => clearInterval(interval)); + }; + }, [library]); + + useEffect(() => { + if (library.length > 0 && title === t("download_in_progress")) { + const game = library[0]; + const heroImageUrl = + game.libraryHeroImageUrl || game.libraryImageUrl || ""; + if (heroImageUrl && game.id) { + extractDominantColor(heroImageUrl, game.id); + } + } + }, [library, title, t, extractDominantColor]); + const isGameSeeding = (game: LibraryGame) => { const entry = seedingStatus.find((s) => s.gameId === game.id); if (entry && entry.status) return entry.status === "seeding"; @@ -141,10 +313,7 @@ export function DownloadGroup({ if (lastPacket.isCheckingFiles) { return t("checking_files"); } - if (lastPacket.timeRemaining && lastPacket.timeRemaining > 0) { - return calculateETA(); - } - return t("calculating_eta"); + return t("download_in_progress"); } if (status === "paused") { @@ -160,32 +329,6 @@ export function DownloadGroup({ return t("paused"); }; - const getSeedsPeersText = (game: LibraryGame) => { - const isGameDownloading = lastPacket?.gameId === game.id; - const isTorrent = game.download?.downloader === Downloader.Torrent; - - if (!isTorrent) return null; - - if (game.download?.progress === 1 && isGameSeeding(game)) { - if ( - isGameDownloading && - (lastPacket.numSeeds > 0 || lastPacket.numPeers > 0) - ) { - return `${lastPacket.numSeeds} seeds, ${lastPacket.numPeers} peers`; - } - return null; - } - - if ( - isGameDownloading && - (lastPacket.numSeeds > 0 || lastPacket.numPeers > 0) - ) { - return `${lastPacket.numSeeds} seeds, ${lastPacket.numPeers} peers`; - } - - return null; - }; - const extractGameDownload = useCallback( async (shop: GameShop, objectId: string) => { await window.electron.extractGameDownload(shop, objectId); @@ -298,6 +441,209 @@ export function DownloadGroup({ if (!library.length) return null; + const isDownloadingGroup = title === t("download_in_progress"); + const isQueuedGroup = title === t("queued_downloads"); + + if (isDownloadingGroup && library.length > 0) { + const game = library[0]; // Only one active download + const isGameDownloading = lastPacket?.gameId === game.id; + const downloadSpeed = isGameDownloading + ? (lastPacket?.downloadSpeed ?? 0) + : 0; + const finalDownloadSize = getFinalDownloadSize(game); + const peakSpeed = peakSpeedsRef.current[game.id] || 0; + const currentProgress = isGameDownloading + ? lastPacket.progress + : game.download?.progress || 0; + + const dominantColor = dominantColors[game.id] || "#ffffff"; + + return ( + <> +
+
+ {game.title} +
+
+ +
+
+
+ + + +
+
+ +
+
+ {game.logoImageUrl ? ( + {game.title} + ) : ( +

{game.title}

+ )} +
+ + {isGameDownloading ? ( + + ) : ( + + )} +
+ +
+
+ + {getStatusText(game)} + + + {formatDownloadProgress(currentProgress)} + +
+
+
{ + try { + const isPaused = game.download?.status === "paused"; + const colorToUse = isPaused + ? "#ffffff" + : dominantColor || "#ffffff"; + const hex = colorToUse; + if (hex.startsWith("#")) { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `linear-gradient(90deg, rgba(${r}, ${g}, ${b}, 0.95) 0%, rgba(${r}, ${g}, ${b}, 0.65) 100%)`; + } + if (hex.startsWith("rgb")) { + const nums = hex.match(/\d+/g) || []; + const r = nums[0] || 8; + const g = nums[1] || 234; + const b = nums[2] || 121; + return `linear-gradient(90deg, rgba(${r}, ${g}, ${b}, 0.95) 0%, rgba(${r}, ${g}, ${b}, 0.65) 100%)`; + } + return undefined; + } catch (e) { + return undefined; + } + })(), + }} + /> +
+
+ + {isGameDownloading && lastPacket + ? `${formatBytes(lastPacket.download.bytesDownloaded)} / ${finalDownloadSize}` + : `0 B / ${finalDownloadSize}`} + + + {isGameDownloading && + lastPacket?.timeRemaining && + lastPacket.timeRemaining > 0 + ? calculateETA() + : ""} + +
+
+ +
+
+
+ + + +
+ + {t("network")}: + + + {isGameDownloading ? formatSpeed(downloadSpeed) : "0 B/s"} + +
+
+ +
+ + + +
+ + {t("peak")}: + + + {peakSpeed > 0 ? formatSpeed(peakSpeed) : "0 B/s"} + +
+
+ + {game.download?.downloader === Downloader.Torrent && + isGameDownloading && + lastPacket && + (lastPacket.numSeeds > 0 || lastPacket.numPeers > 0) && ( +
+
+ + Seeds:{" "} + + {lastPacket.numSeeds} + + , Peers:{" "} + + {lastPacket.numPeers} + + +
+
+ )} +
+ +
+ +
+
+
+
+ + ); + } + return (
@@ -306,186 +652,83 @@ export function DownloadGroup({

{library.length}

-
    +
      {library.map((game) => { - const isGameDownloading = lastPacket?.gameId === game.id; - const downloadSpeed = isGameDownloading - ? (lastPacket?.downloadSpeed ?? 0) - : 0; const finalDownloadSize = getFinalDownloadSize(game); - const peakSpeed = peakSpeedsRef.current[game.id] || 0; - - const currentProgress = isGameDownloading - ? lastPacket.progress - : game.download?.progress || 0; + const currentProgress = game.download?.progress || 0; return ( -
    • -
      - {game.title} -
      +
    • +
      + {game.title}
      -
      -
      -
      - {game.logoImageUrl ? ( - {game.title} - ) : ( -

      - {game.title} -

      - )} -
      - - {DOWNLOADER_NAME[game.download!.downloader]} - -
      -
      -
      -
      -
      -
      -
      - -
      - - NETWORK - - - {isGameDownloading - ? formatSpeed(downloadSpeed) - : "0 B/s"} - -
      -
      -
      - -
      - - PEAK - - - {peakSpeed > 0 ? formatSpeed(peakSpeed) : "0 B/s"} - -
      -
      -
      - -
      - - size on DISK - - - {finalDownloadSize} - -
      -
      -
      - - {getGameActions(game) !== null && ( - - - - )} -
      - -
      -
      -
      - - {game.download?.extracting || isGameDeleting(game.id) - ? getStatusText(game) - : formatDownloadProgress(currentProgress)} - - {isGameDownloading && ( - - {formatBytes(lastPacket.download.bytesDownloaded)} /{" "} - {finalDownloadSize} - - )} -
      -
      -
      -
      - -
      - {getStatusText(game)} - {getSeedsPeersText(game) && ( - - • {getSeedsPeersText(game)} - - )} -
      -
      - -
      - {game.download?.progress === 1 ? ( - - ) : isGameDownloading ? ( - - ) : ( - - )} -
      -
      +
      +

      {game.title}

      +
      + {DOWNLOADER_NAME[game.download!.downloader]} + + {finalDownloadSize} + + {game.download?.progress === 1 && isGameSeeding(game) && ( + + {t("seeding")} + + )}
      - {game.download?.downloader === Downloader.Hydra && ( -
      + {isQueuedGroup && ( +
      + + {formatDownloadProgress(currentProgress)} + +
      +
      { + try { + const isPaused = game.download?.status === "paused"; + const colorToUse = isPaused + ? "#ffffff" + : dominantColors[game.id] || "#ffffff"; + const hex = colorToUse; + if (hex.startsWith("#")) { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `linear-gradient(90deg, rgba(${r}, ${g}, ${b}, 0.95) 0%, rgba(${r}, ${g}, ${b}, 0.65) 100%)`; + } + if (hex.startsWith("rgb")) { + const nums = hex.match(/\d+/g) || []; + const r = nums[0] || 8; + const g = nums[1] || 234; + const b = nums[2] || 121; + return `linear-gradient(90deg, rgba(${r}, ${g}, ${b}, 0.95) 0%, rgba(${r}, ${g}, ${b}, 0.65) 100%)`; + } + return undefined; + } catch (e) { + return undefined; + } + })(), + }} + /> +
      +
      )} + +
      + + + +
    • ); })} diff --git a/src/renderer/src/pages/downloads/downloads.scss b/src/renderer/src/pages/downloads/downloads.scss index 8290a66e..abada8d7 100644 --- a/src/renderer/src/pages/downloads/downloads.scss +++ b/src/renderer/src/pages/downloads/downloads.scss @@ -3,7 +3,6 @@ .downloads { &__container { display: flex; - padding: calc(globals.$spacing-unit * 3); flex-direction: column; width: 100%; }