pixelkiln 0.20.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PROVIDERS.md CHANGED
@@ -85,7 +85,7 @@ prompt, size, batch, and optional seed inputs PixelKiln may replace.
85
85
  | PixelKiln generator | `map` stills and atomic `frames` sets |
86
86
  | Output | One PNG output node: 1–16 map candidates or 2–64 ordered still frames |
87
87
  | Revision input | `image-to-image`, `inpaint`, and user-authored `outpaint` graphs; hashed parent/mask uploads and source comparison are covered. The bundled img2img graph passed a three-strength live smoke but missed the requested winter treatment and lost alpha. |
88
- | Cost model | `0 free`; local compute and hosting are outside PixelKiln's estimate |
88
+ | Cost model | No provider charge; local compute and hosting are outside PixelKiln's estimate |
89
89
  | Reproducibility | Workflow content is hashed; model files, custom-node versions, and runtime settings must still be managed outside PixelKiln |
90
90
  | Account lifecycle | Read-only connectivity check; no balance, remote object listing, tagging, or purge |
91
91
  | Confidence | Live map generation, four-candidate review, cache-only restore, and refinement on Apple MPS; deterministic coverage for frame submission, ordered review/fetch, shared-grid and palette gating, approval, and packing |
package/dist/cli.js CHANGED
@@ -6986,7 +6986,10 @@ function renderSheet(groups) {
6986
6986
  .loop { display:flex; align-items:flex-start; gap:12px; margin-bottom:12px; }
6987
6987
  .loop img { image-rendering:pixelated; border:2px solid var(--accent); padding:7px;
6988
6988
  background:var(--panel-deep); max-width:min(var(--preview),calc(100vw - 62px)); height:auto; }
6989
- .loop-copy { color:var(--dim); font-size:11px; max-width:32ch; }
6989
+ .loop-copy { color:var(--dim); font-size:11px; max-width:32ch; display:flex;
6990
+ flex-direction:column; gap:8px; }
6991
+ .loop-copy p { margin:0; }
6992
+ .loop-copy button { align-self:flex-start; padding:5px 9px; font-size:10.5px; }
6990
6993
  .cand { border:2px solid var(--line-strong); border-radius:0; padding:7px; background:var(--panel-deep);
6991
6994
  cursor:pointer; display:flex; flex:0 0 auto; flex-direction:column; align-items:center; gap:5px;
6992
6995
  position:relative; max-width:100%; }
@@ -7090,15 +7093,49 @@ GROUPS.forEach((g, gi) => {
7090
7093
  preview.alt = 'Animated frame-set preview';
7091
7094
  const copy = document.createElement('div');
7092
7095
  copy.className = 'loop-copy';
7093
- copy.textContent = g.frameUrls.length + ' ordered frames \xB7 ' + (g.fps || 12) +
7096
+ const copyText = document.createElement('p');
7097
+ copyText.textContent = g.frameUrls.length + ' ordered frames \xB7 ' + (g.fps || 12) +
7094
7098
  ' fps. Accepting keeps every frame; one bad frame means leave the set unchosen.';
7099
+ const playback = document.createElement('button');
7100
+ playback.type = 'button';
7101
+ let userPaused = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
7102
+ let loopVisible = true;
7103
+ let pageVisible = !document.hidden;
7104
+ let loopTimer = null;
7105
+ let loopIndex = 0;
7106
+ const stopLoop = () => {
7107
+ if (loopTimer !== null) window.clearInterval(loopTimer);
7108
+ loopTimer = null;
7109
+ };
7110
+ const syncLoop = () => {
7111
+ stopLoop();
7112
+ playback.textContent = userPaused ? 'Play preview' : 'Pause preview';
7113
+ playback.setAttribute('aria-pressed', String(userPaused));
7114
+ if (!userPaused && loopVisible && pageVisible) {
7115
+ loopTimer = window.setInterval(() => {
7116
+ loopIndex = (loopIndex + 1) % g.frameUrls.length;
7117
+ preview.src = g.frameUrls[loopIndex];
7118
+ }, Math.max(16, Math.round(1000 / (g.fps || 12))));
7119
+ }
7120
+ };
7121
+ playback.addEventListener('click', () => {
7122
+ userPaused = !userPaused;
7123
+ syncLoop();
7124
+ });
7125
+ copy.append(copyText, playback);
7095
7126
  loop.append(preview, copy);
7096
7127
  review.insertBefore(loop, frames);
7097
- let loopIndex = 0;
7098
- setInterval(() => {
7099
- loopIndex = (loopIndex + 1) % g.frameUrls.length;
7100
- preview.src = g.frameUrls[loopIndex];
7101
- }, Math.max(16, Math.round(1000 / (g.fps || 12))));
7128
+ document.addEventListener('visibilitychange', () => {
7129
+ pageVisible = !document.hidden;
7130
+ syncLoop();
7131
+ });
7132
+ syncLoop();
7133
+ if ('IntersectionObserver' in window) {
7134
+ new IntersectionObserver((entries) => {
7135
+ loopVisible = entries[0]?.isIntersecting ?? false;
7136
+ syncLoop();
7137
+ }).observe(loop);
7138
+ }
7102
7139
  }
7103
7140
  g.frameUrls.forEach((url, i) => {
7104
7141
  const c = document.createElement('button');