From db52a2b589eb641f2bd65c81ff07efaa7350f00d Mon Sep 17 00:00:00 2001 From: mrleo1nid <43583385+mrleo1nid@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:00:26 +0300 Subject: [PATCH] feat: collapse background uploads into a progress circle and fit hero covers --- frontend/src/styles.css | 22 +++- frontend/src/ui/Icon.tsx | 1 + frontend/src/uploads.tsx | 44 +++++++- frontend/tests/upload-panel-browser.test.mjs | 112 +++++++++++++++++++ 4 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 frontend/tests/upload-panel-browser.test.mjs diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 7e21b42..f095b29 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -595,15 +595,18 @@ input[type="file"] { padding: 0.4rem; } z-index: -1; background: linear-gradient(to bottom, transparent 20%, var(--bg) 100%); } -.hero-inner { display: flex; gap: var(--space-5); align-items: flex-end; max-width: 1500px; margin: 0 auto; flex-wrap: wrap; } +.hero-inner { display: flex; gap: var(--space-5); align-items: flex-start; max-width: 1500px; margin: 0 auto; flex-wrap: wrap; } .hero .cover.big { width: 380px; max-width: 100%; + aspect-ratio: auto; border-radius: var(--radius-l); box-shadow: var(--shadow-3); border: 1px solid var(--border); flex-shrink: 0; } +.hero .cover.big img { height: auto; object-fit: contain; } +.hero .cover.big > span { width: 100%; aspect-ratio: 16 / 10; display: grid; place-items: center; } .hero-info { flex: 1; min-width: 280px; display: flex; flex-direction: column; gap: var(--space-3); } .hero-info h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); } .description { white-space: pre-wrap; color: var(--text-2); max-width: 70ch; } @@ -679,6 +682,23 @@ input[type="file"] { padding: 0.4rem; } .upload-floating { position: fixed; right: var(--space-4); bottom: var(--space-4); z-index: 50; width: min(360px, calc(100vw - 2rem)); padding: var(--space-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-2); max-height: calc(100dvh - var(--nav-h) - 2rem); overflow-y: auto; } .upload-floating .upload-progress { padding: 0; border: 0; box-shadow: none; } .upload-floating > * + * { margin-top: var(--space-3); } +.upload-minimize { position: absolute; top: var(--space-4); right: var(--space-4); } +.upload-minimize + .upload-progress { margin-top: 0; } +.upload-floating .upload-progress h2 { padding-right: 2rem; } +.upload-floating.upload-floating-collapsed { width: auto; padding: 0; border: 0; background: transparent; box-shadow: none; overflow: visible; } +.upload-bubble { position: relative; display: grid; place-items: center; width: 64px; height: 64px; padding: 0; border: 1px solid var(--border); border-radius: 50%; background: var(--surface); color: var(--accent); box-shadow: var(--shadow-2); cursor: pointer; } +.upload-bubble:hover { background: var(--surface-2); } +.upload-bubble.done { color: var(--ok); } +.upload-bubble.failed { color: var(--danger); } +.upload-ring { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); fill: none; stroke-width: 4; } +.upload-ring-track { stroke: var(--surface-3); } +.upload-ring-value { stroke: currentColor; stroke-linecap: round; transition: stroke-dasharray 0.2s; } +.upload-ring.indeterminate { animation: upload-ring-spin 1.4s linear infinite; } +.upload-bubble-value { display: grid; place-items: center; font-size: 0.75rem; font-weight: 700; } +.upload-bubble-count { position: absolute; top: -3px; right: -3px; display: grid; place-items: center; min-width: 21px; height: 21px; padding: 0 4px; border: 1px solid var(--border); border-radius: 50%; background: var(--surface-2); color: var(--text); font-size: 0.7rem; } +.upload-bubble-count.has-error { background: var(--danger); color: white; } +@keyframes upload-ring-spin { to { transform: rotate(270deg); } } +@media (prefers-reduced-motion: reduce) { .upload-ring.indeterminate { animation: none; } } @media (max-width: 700px) { .upload-floating { bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 1rem); max-height: calc(100dvh - var(--nav-h) - var(--tabbar-h) - env(safe-area-inset-bottom) - 2rem); } } diff --git a/frontend/src/ui/Icon.tsx b/frontend/src/ui/Icon.tsx index de1d2e6..4f3f6bb 100644 --- a/frontend/src/ui/Icon.tsx +++ b/frontend/src/ui/Icon.tsx @@ -18,6 +18,7 @@ const PATHS = { edit: "M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z", image: "M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Zm5.5 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM21 15l-5-5L5 21", plus: "M12 5v14M5 12h14", + minus: "M5 12h14", back: "M19 12H5m7-7-7 7 7 7", chevron: "m9 6 6 6-6 6", check: "m5 12 5 5L20 7", diff --git a/frontend/src/uploads.tsx b/frontend/src/uploads.tsx index 36f394d..1c481ee 100644 --- a/frontend/src/uploads.tsx +++ b/frontend/src/uploads.tsx @@ -157,12 +157,48 @@ function FloatingUploads() { const { pathname } = useLocation(); const { tasks } = useUploads(); const { task: translation } = useTranslationRun(); + const [collapsed, setCollapsed] = useState(false); // on the upload page the uploads are listed in the page itself const uploads = tasks.length > 0 && pathname.replace(/\/$/, "") !== "/games/new"; if (!uploads && !translation) return null; - return