From 8c36195731a599f8915c0704216f01cf79c864e0 Mon Sep 17 00:00:00 2001 From: rubiidev18alt <164731988+rubiidev18alt@users.noreply.github.com> Date: Fri, 13 Mar 2026 16:27:48 -0700 Subject: [PATCH 1/2] Left-align download status row and shrink spinner --- index.html | 7 ++++-- renderer.js | 29 ++++++++++++++++++++-- style.css | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index cfc3c50..fa6a388 100644 --- a/index.html +++ b/index.html @@ -22,7 +22,7 @@
-
CONNECTING...
+
CONNECTING
@@ -92,7 +92,10 @@
-
Downloading...
+
+ +
Downloading
+
diff --git a/renderer.js b/renderer.js index df621a9..5c2230c 100644 --- a/renderer.js +++ b/renderer.js @@ -1125,7 +1125,11 @@ async function fetchGitHubData() { const offlineInd = document.getElementById('offline-indicator'); if (loader) loader.style.display = 'flex'; - if (loaderText) loaderText.textContent = "SYNCING: " + repo; + if (loaderText) { + const loaderLabel = document.getElementById('loader-text-label'); + if (loaderLabel) loaderLabel.textContent = "SYNCING: " + repo; + else loaderText.textContent = "SYNCING: " + repo; + } const hideLoader = () => { if (loader) { @@ -1416,8 +1420,29 @@ function setProcessingState(active) { function updateProgress(percent, text) { const bar = document.getElementById('progress-bar-fill'); if (bar) bar.style.width = percent + "%"; + + const label = document.getElementById('progress-text-label'); + const dots = document.getElementById('progress-dots'); + const suffix = document.getElementById('progress-text-suffix'); const txt = document.getElementById('progress-text'); - if (text && txt) txt.textContent = text; + + if (!text) return; + + if (label && dots && suffix) { + const match = text.match(/^(Downloading)(?:\.{0,3})?(.*)$/i); + if (match) { + label.textContent = match[1]; + suffix.textContent = match[2] || ''; + dots.classList.add('animate'); + return; + } + label.textContent = text; + suffix.textContent = ''; + dots.classList.remove('animate'); + return; + } + + if (txt) txt.textContent = text; } async function handleElectronFlow(url) { diff --git a/style.css b/style.css index bd8aabc..07ae620 100644 --- a/style.css +++ b/style.css @@ -568,12 +568,71 @@ body.steamdeck-mode .sidebar { transition: width 0.2s ease-out; } +.progress-text-wrap { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 8px; + margin-bottom: 6px; +} + +.progress-spinner { + width: 12px; + height: 12px; + border-width: 2px; + border-color: #333; + border-top-color: var(--mc-progress-fill); + margin: 0; + flex-shrink: 0; + animation-duration: 1s; +} + .progress-text { color: #fff; font-size: 20px; text-shadow: 2px 2px 0 #000; - margin-bottom: 6px; - text-align: center; + text-align: left; + display: inline-flex; + align-items: center; +} + +.loading-dots { + display: inline-flex; + width: 1.8em; + justify-content: flex-start; +} + +.loading-dots span { + visibility: hidden; +} + +.loading-dots.animate span:nth-child(1) { + animation: dotCycle1 1.1s steps(1, end) infinite; +} + +.loading-dots.animate span:nth-child(2) { + animation: dotCycle2 1.1s steps(1, end) infinite; +} + +.loading-dots.animate span:nth-child(3) { + animation: dotCycle3 1.1s steps(1, end) infinite; +} + +@keyframes dotCycle1 { + 0%, 49% { visibility: hidden; } + 50%, 100% { visibility: visible; } +} + +@keyframes dotCycle2 { + 0%, 32% { visibility: hidden; } + 33%, 66% { visibility: visible; } + 67%, 100% { visibility: hidden; } +} + +@keyframes dotCycle3 { + 0%, 16% { visibility: hidden; } + 17%, 82% { visibility: visible; } + 83%, 100% { visibility: hidden; } } .modal-overlay { @@ -803,12 +862,18 @@ body.steamdeck-mode .sidebar { background: rgba(0,0,0,0.8); } + .loader-content { position: relative; z-index: 1; text-align: center; } +#loader-text { + display: inline-flex; + align-items: center; +} + .loader-spinner { width: 80px; height: 80px; From 4858f83ea1947aac6a7d09fbe25ae30ee3cdfef6 Mon Sep 17 00:00:00 2001 From: rubiidev18alt <164731988+rubiidev18alt@users.noreply.github.com> Date: Fri, 13 Mar 2026 16:49:42 -0700 Subject: [PATCH 2/2] Keep 2D skin mode fully static --- index.html | 7 +++-- skin_manager.js | 79 +++++++++++++++++++++++++++++++++++++++++++++---- style.css | 24 +++++++++++++++ 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index fa6a388..785a6f8 100644 --- a/index.html +++ b/index.html @@ -86,8 +86,11 @@
- diff --git a/skin_manager.js b/skin_manager.js index 031a496..a3da23f 100644 --- a/skin_manager.js +++ b/skin_manager.js @@ -3,6 +3,7 @@ let mainMenuScene, mainMenuCamera, mainMenuRenderer, mainMenuPlayerGroup; let isMainSkinDragging = false; +let mainMenuSkinRenderMode = '3d'; let skinScene, skinCamera, skinRenderer, skinPlayerGroup; let isSkinDragging = false; @@ -75,12 +76,13 @@ function initMainMenuSkinViewer() { // Interaction for Main Menu Viewer let prevX = 0; container.addEventListener('mousedown', (e) => { + if (mainMenuSkinRenderMode !== '3d') return; isMainSkinDragging = true; prevX = e.clientX; }); window.addEventListener('mouseup', () => isMainSkinDragging = false); window.addEventListener('mousemove', (e) => { - if (isMainSkinDragging && mainMenuPlayerGroup) { + if (mainMenuSkinRenderMode === '3d' && isMainSkinDragging && mainMenuPlayerGroup) { mainMenuPlayerGroup.rotation.y += (e.clientX - prevX) * 0.01; prevX = e.clientX; } @@ -89,7 +91,9 @@ function initMainMenuSkinViewer() { // Auto-rotate slowly function animateMain() { requestAnimationFrame(animateMain); - if (!isMainSkinDragging && mainMenuPlayerGroup) mainMenuPlayerGroup.rotation.y += 0.005; + if (mainMenuSkinRenderMode === '3d' && !isMainSkinDragging && mainMenuPlayerGroup) { + mainMenuPlayerGroup.rotation.y += 0.005; + } if (mainMenuRenderer && mainMenuScene && mainMenuCamera) mainMenuRenderer.render(mainMenuScene, mainMenuCamera); } animateMain(); @@ -123,7 +127,7 @@ async function loadMainMenuSkin() { const img = new Image(); img.onload = () => { const isLegacy = img.height === 32; - updateSkinModel(img.src, isLegacy, mainMenuPlayerGroup); + updateSkinModel(img.src, isLegacy, mainMenuPlayerGroup, mainMenuSkinRenderMode); }; img.src = url; } else { @@ -134,7 +138,25 @@ async function loadMainMenuSkin() { } } -function updateSkinModel(dataUrl, isLegacy, targetGroup) { +function toggleMainSkinRenderMode() { + mainMenuSkinRenderMode = mainMenuSkinRenderMode === '3d' ? '2d' : '3d'; + const modeButton = document.getElementById('btn-skin-render-mode'); + if (modeButton) modeButton.textContent = mainMenuSkinRenderMode.toUpperCase(); + + const container = document.getElementById('main-skin-viewer'); + if (container) { + container.style.cursor = mainMenuSkinRenderMode === '3d' ? 'grab' : 'default'; + } + isMainSkinDragging = false; + + if (mainMenuSkinRenderMode === '2d' && mainMenuPlayerGroup) { + mainMenuPlayerGroup.rotation.y = 0; + } + + loadMainMenuSkin(); +} + +function updateSkinModel(dataUrl, isLegacy, targetGroup, renderMode = '3d') { if (!targetGroup) return; new THREE.TextureLoader().load(dataUrl, (texture) => { @@ -178,6 +200,50 @@ function updateSkinModel(dataUrl, isLegacy, targetGroup) { left: [x+8, y+4, 4, 12], back: [x+12, y+4, 4, 12] }); + const create2DPart = (tex, uvFront, width, height, x, y, scale = 1) => { + const texWidth = tex.image.width; + const texHeight = tex.image.height; + const partTex = tex.clone(); + partTex.magFilter = THREE.NearestFilter; + partTex.minFilter = THREE.NearestFilter; + partTex.repeat.set(uvFront[2] / texWidth, uvFront[3] / texHeight); + partTex.offset.set(uvFront[0] / texWidth, 1 - (uvFront[1] + uvFront[3]) / texHeight); + partTex.needsUpdate = true; + + const geometry = new THREE.PlaneGeometry(width, height); + const material = new THREE.MeshBasicMaterial({ map: partTex, transparent: true, alphaTest: 0.5, side: THREE.DoubleSide }); + const mesh = new THREE.Mesh(geometry, material); + mesh.position.set(x, y, 0); + mesh.scale.set(scale, scale, 1); + return mesh; + }; + + if (renderMode === '2d') { + const frontParts = [ + create2DPart(texture, [8, 8, 8, 8], 8, 8, 0, 10), + create2DPart(texture, [40, 8, 8, 8], 8, 8, 0, 10, 1.12), + create2DPart(texture, [20, 20, 8, 12], 8, 12, 0, 0), + create2DPart(texture, [44, 20, 4, 12], 4, 12, -6, 0), + create2DPart(texture, isLegacy ? [44, 20, 4, 12] : [36, 52, 4, 12], 4, 12, 6, 0), + create2DPart(texture, [4, 20, 4, 12], 4, 12, -2, -12), + create2DPart(texture, isLegacy ? [4, 20, 4, 12] : [20, 52, 4, 12], 4, 12, 2, -12) + ]; + + if (!isLegacy) { + frontParts.push( + create2DPart(texture, [20, 36, 8, 12], 8, 12, 0, 0, 1.05), + create2DPart(texture, [44, 36, 4, 12], 4, 12, -6, 0, 1.05), + create2DPart(texture, [52, 52, 4, 12], 4, 12, 6, 0, 1.05), + create2DPart(texture, [4, 36, 4, 12], 4, 12, -2, -12, 1.05), + create2DPart(texture, [4, 52, 4, 12], 4, 12, 2, -12, 1.05) + ); + } + + frontParts.forEach((part) => targetGroup.add(part)); + targetGroup.rotation.y = 0; + return; + } + // Head const headUvs = { top: [8, 0, 8, 8], bottom: [16, 0, 8, 8], right: [0, 8, 8, 8], left: [16, 8, 8, 8], front: [8, 8, 8, 8], back: [24, 8, 8, 8] }; const head = createBodyPart(8, 8, 8, texture, headUvs); @@ -218,6 +284,8 @@ function updateSkinModel(dataUrl, isLegacy, targetGroup) { targetGroup.add(layer); } }); + + targetGroup.rotation.y = 0; }); } @@ -318,7 +386,7 @@ function processSkinImage(img, srcUrl, isInitialLoad = false) { } if (!skinScene) initPreviewEngine(); - updateSkinModel(srcUrl, isLegacy, skinPlayerGroup); + updateSkinModel(srcUrl, isLegacy, skinPlayerGroup, '3d'); } function initPreviewEngine() { @@ -398,3 +466,4 @@ async function saveSkinToDisk() { window.openSkinManager = openSkinManager; window.initMainMenuSkinViewer = initMainMenuSkinViewer; window.loadMainMenuSkin = loadMainMenuSkin; +window.toggleMainSkinRenderMode = toggleMainSkinRenderMode; diff --git a/style.css b/style.css index 07ae620..3ddc137 100644 --- a/style.css +++ b/style.css @@ -1398,3 +1398,27 @@ body.classic-theme .sidebar-title { body.classic-theme .music-btn { bottom: 92px; } + +.skin-action-row { + display: flex; + align-items: stretch; + justify-content: center; +} + +.skin-action-btn { + height: 48px !important; + margin-bottom: 0 !important; +} + +.skin-action-btn-main { + width: 250px; + font-size: 20px; + border-right-width: 1px; +} + +.skin-action-btn-mode { + width: 64px; + font-size: 20px; + border-left-width: 1px; + margin-left: -2px; +}