Make the barber-pole progress bar loop seamlessly

The stripes were 45deg with a 14px period, which repeats every 19.8px
horizontally, but the background tile and animation step were 28px, so
every tile cut a stripe and the loop visibly jumped. Stripe spacing is
now 28/sqrt(2) so the horizontal period is exactly 28px, painted once
on an untiled layer 28px wider than the bar and slid by one period with
a transform. Verified by comparing rendered frames: the end of the loop
is pixel-identical to its start.

Error and warning states now use a data-tone attribute (solid red or
yellow, no stripes) instead of inline backgrounds.
This commit is contained in:
Andy Dixon
2026-08-23 19:07:23 +01:00
parent b07095d789
commit e2f9c317c6
2 changed files with 11 additions and 5 deletions
+3 -3
View File
@@ -382,7 +382,7 @@ function showPrivateLinkWarning(place,authRequired){
$('#progressMessage').textContent=authRequired==='install' $('#progressMessage').textContent=authRequired==='install'
?`This link points to ${repoName(place.repo)}, a private repository your GitHub account can't open here. Grant access to it, then open the link again.` ?`This link points to ${repoName(place.repo)}, a private repository your GitHub account can't open here. Grant access to it, then open the link again.`
:`This link points to ${repoName(place.repo)}, a private repository, and your GitHub access has expired (or you haven't signed in on this device). Sign in with GitHub to carry on to the shared spot.`; :`This link points to ${repoName(place.repo)}, a private repository, and your GitHub access has expired (or you haven't signed in on this device). Sign in with GitHub to carry on to the shared spot.`;
$('#progressFill').style.background='var(--yellow)'; $('#progressFill').dataset.tone='warn';
$('#progressCount').textContent=''; $('#progressCount').textContent='';
} }
@@ -1326,8 +1326,8 @@ async function watchJob(jobId){
}catch(error){if(!controller.signal.aborted)showError(error.message);} }catch(error){if(!controller.signal.aborted)showError(error.message);}
finally{if(activeJobStream===controller)activeJobStream=null;controller.abort();} finally{if(activeJobStream===controller)activeJobStream=null;controller.abort();}
} }
function showProgress(title,message,completed,total){$('#progressCard').hidden=false;$('#progressAction').hidden=true;$('#progressTitle').textContent=title;$('#progressMessage').textContent=message||'';const percent=total?Math.max(3,Math.round(completed/total*100)):12;$('#progressFill').style.width=`${percent}%`;$('#progressFill').style.background='';$('#progressCount').textContent=total?`${format(completed)} / ${format(total)} · ${percent}%`:'Working…';} function showProgress(title,message,completed,total){$('#progressCard').hidden=false;$('#progressAction').hidden=true;$('#progressTitle').textContent=title;$('#progressMessage').textContent=message||'';const percent=total?Math.max(3,Math.round(completed/total*100)):12;$('#progressFill').style.width=`${percent}%`;delete $('#progressFill').dataset.tone;$('#progressCount').textContent=total?`${format(completed)} / ${format(total)} · ${percent}%`:'Working…';}
function showError(message,authRequired){$('#progressCard').hidden=false;showAuthAction(authRequired);$('#progressTitle').textContent='Could not build landscape';$('#progressMessage').textContent=message;$('#progressFill').style.width='100%';$('#progressFill').style.background='var(--danger)';$('#progressCount').textContent='Check the repository or folder and try again.';} function showError(message,authRequired){$('#progressCard').hidden=false;showAuthAction(authRequired);$('#progressTitle').textContent='Could not build landscape';$('#progressMessage').textContent=message;$('#progressFill').style.width='100%';$('#progressFill').dataset.tone='error';$('#progressCount').textContent='Check the repository or folder and try again.';}
// Private repositories: sign in (or grant the GitHub App access), then resume the same repository on return. // Private repositories: sign in (or grant the GitHub App access), then resume the same repository on return.
let lastGithubUrl='',githubStatus={}; let lastGithubUrl='',githubStatus={};
+8 -2
View File
@@ -44,7 +44,7 @@ kbd { display: inline-block; min-width: 18px; padding: 0 4px; border: 2px solid
@keyframes wiggle { 25% { transform: rotate(-9deg); } 75% { transform: rotate(7deg); } } @keyframes wiggle { 25% { transform: rotate(-9deg); } 75% { transform: rotate(7deg); } }
@keyframes blink { 50% { opacity: .35; } } @keyframes blink { 50% { opacity: .35; } }
@keyframes barber { to { background-position: 28px 0; } } @keyframes barber { from { transform: translateX(-28px); } to { transform: translateX(0); } }
@keyframes plonk { from { opacity: 0; transform: translateY(-10px) rotate(-3deg); } } @keyframes plonk { from { opacity: 0; transform: translateY(-10px) rotate(-3deg); } }
@keyframes bob { 50% { transform: rotate(4deg) translateY(-4px); } } @keyframes bob { 50% { transform: rotate(4deg) translateY(-4px); } }
@@ -268,7 +268,13 @@ kbd { display: inline-block; min-width: 18px; padding: 0 4px; border: 2px solid
.progress-copy span, .progress-count { color: var(--muted); } .progress-copy span, .progress-count { color: var(--muted); }
#progressMessage { max-height: 100px; overflow: auto; overflow-wrap: anywhere; font: 11px var(--mono); } #progressMessage { max-height: 100px; overflow: auto; overflow-wrap: anywhere; font: 11px var(--mono); }
.progress-track { position: relative; height: 12px; margin: 12px 0 7px; overflow: hidden; border: 2px solid var(--black); border-radius: 3px; background: var(--ink); } .progress-track { position: relative; height: 12px; margin: 12px 0 7px; overflow: hidden; border: 2px solid var(--black); border-radius: 3px; background: var(--ink); }
.progress-track i { display: block; height: 100%; width: 5%; background: repeating-linear-gradient(-45deg, var(--lime) 0 7px, var(--black) 7px 14px) 0 0 / 28px 100%; animation: barber .6s linear infinite; transition: width .25s ease; } .progress-track i { position: relative; display: block; height: 100%; width: 5%; overflow: hidden; background: var(--lime); transition: width .25s ease; }
/* Barber pole: 45deg stripes whose horizontal period is exactly 28px (stripe spacing 28/sqrt 2), painted
once on a layer 28px wider than the bar and slid one period, so the loop has no seam or jump. */
.progress-track i::before { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: calc(100% + 28px); background: repeating-linear-gradient(-45deg, var(--lime) 0 calc(14px / 1.41421356), var(--black) 0 calc(28px / 1.41421356)); animation: barber .6s linear infinite; will-change: transform; }
.progress-track i[data-tone="error"] { background: var(--danger); }
.progress-track i[data-tone="warn"] { background: var(--yellow); }
.progress-track i[data-tone]::before { display: none; }
.progress-count { font: 10.5px var(--mono); } .progress-count { font: 10.5px var(--mono); }
.progress-dismiss { position: absolute; right: 8px; top: 8px; } .progress-dismiss { position: absolute; right: 8px; top: 8px; }
.progress-action { display: inline-grid; place-items: center; width: fit-content; margin-top: 14px; text-decoration: none; } .progress-action { display: inline-grid; place-items: center; width: fit-content; margin-top: 14px; text-decoration: none; }