From 7ea2da46ab3941208a4038389f5489a09c75c51f Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Thu, 13 Aug 2026 01:03:38 -0400 Subject: [PATCH] Fix: keep the backup placeholder fixed across card faces Seed the placeholder drawing per card so both faces of a card render the identical pattern and a redraw repeats it. Give the back canvas the same height as the front canvas so the row plan does not change between faces. --- CHANGELOG.md | 2 ++ ui/widgets/backup/backup.html | 23 ++++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e7a3a02..0b3c57b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A long error in Admin now stays on screen long enough to read, up to fifteen seconds. +- Backup placeholder no longer moves or reshuffles when a card is hovered or + tapped. ## [1.6.0] - 2026-08-13 diff --git a/ui/widgets/backup/backup.html b/ui/widgets/backup/backup.html index a41ded6f..690a05a8 100644 --- a/ui/widgets/backup/backup.html +++ b/ui/widgets/backup/backup.html @@ -192,7 +192,7 @@ } /* ── Canvas placeholder drawing ── */ -function drawPh(canvas, haiku, isFront) { +function drawPh(canvas, haiku, isFront, seed) { const dpr = Math.min(window.devicePixelRatio || 1, 2); /* cap DPR at 2 */ const W = canvas.offsetWidth; const H = canvas.offsetHeight; @@ -204,7 +204,9 @@ ctx.scale(dpr, dpr); ctx.clearRect(0, 0, W, H); - const rng = Math.random.bind(Math); + /* Both faces of a card must draw the same pattern, and a redraw must repeat + it. Never use Math.random here. */ + const rng = mkRng(seed); /* Colors */ const dotC = isFront ? 'rgba(0,0,0,0.08)' : 'rgba(255,255,255,0.35)'; @@ -302,13 +304,13 @@ /* Draw once the canvas has a real size. A card whose height comes from aspect-ratio layout has no size at draw time. */ -function observeDraw(canvas, haiku, isFront) { +function observeDraw(canvas, haiku, isFront, seed) { let lastW = 0, lastH = 0; const ro = new ResizeObserver(() => { const w = canvas.offsetWidth, h = canvas.offsetHeight; if (!w || !h || (w === lastW && h === lastH)) return; lastW = w; lastH = h; - drawPh(canvas, haiku, isFront); + drawPh(canvas, haiku, isFront, seed); }); ro.observe(canvas); } @@ -470,7 +472,10 @@ back.className = 'card-back'; back.id = `back-${idx}`; const backCanvas = document.createElement('canvas'); - if (isMobile) backCanvas.style.height = 'calc(100% - 100px)'; + /* Same height as the front canvas. A different height changes the row plan + and the placeholder moves when the card is hovered. The name zone is + opaque and covers the extra rows. */ + if (isMobile) backCanvas.style.height = 'calc(100% - 48px)'; back.appendChild(backCanvas); const backZone = document.createElement('div'); backZone.className = 'card-name-zone'; @@ -511,11 +516,11 @@ wrap.append(back, front); w.appendChild(wrap); - _canvases[idx] = { front: frontCanvas, back: backCanvas, haiku }; + _canvases[idx] = { front: frontCanvas, back: backCanvas, haiku, seed }; /* Draw when each canvas actually has its size (and redraw on real resize) */ - observeDraw(frontCanvas, haiku, true); - observeDraw(backCanvas, haiku, false); + observeDraw(frontCanvas, haiku, true, seed); + observeDraw(backCanvas, haiku, false, seed); }); } @@ -558,7 +563,7 @@ /* Redraw back canvas with updated text color when status changes */ const c = _canvases[idx]; if (c && c.back.offsetWidth && c.back.offsetHeight) { - drawPh(c.back, c.haiku, false); + drawPh(c.back, c.haiku, false, c.seed); } if(bname) bname.textContent = job.name||'—';